0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

ROS2講座09 SQLiteをC++から使う

Posted at

環境

この記事は以下の環境で動いています。

項目
CPU Core i5-8250U
Ubuntu 22.04
ROS2 Humble
SQLite 3.37.2

概要

前回でc++でSQLiteを使う基礎的なコードを紹介しました。ここではSELECTの結果を取り出す、ステートメントでSQL命令を書くの2つのAPIの使い方を紹介します。

SELECTの結果を取り出す

上記のサンプルではデータ操作をするコマンドを

ソースコード

sqlite_lecture/src/sample2/main.cpp
#include <sqlite3.h>
#include <sqlite_lecture/relation_data.h>

int exec_callback(void *data, int argc, char **argv, char **azColName)
{
  RelationData *relation_data = (RelationData *)data;
  relation_data->increment();
  for (int i = 0; i < argc; i++)
  {
    relation_data->setAtBack(azColName[i], argv[i]);
  }
  return 0;
};

int main(int argc, char *argv[])
{
  printf("Start\n");

  sqlite3 *db = NULL;
  if (sqlite3_open("data.db", &db) != SQLITE_OK)
  {
    printf("%s[%s]\n", __func__, sqlite3_errmsg(db));
    return -1;
  }

  // create Table
  if (sqlite3_exec(db, "CREATE TABLE IF NOT EXISTS CountList (name PRIMARY KEY, count);", NULL, NULL, NULL) != SQLITE_OK){
    printf("%s[%s]\n", __func__, sqlite3_errmsg(db));
    return -1;
  }

  // insert record
  if (sqlite3_exec(db, "INSERT OR IGNORE INTO CountList VALUES ('boot', 0);", NULL, NULL, NULL) != SQLITE_OK){
    printf("%s[%s]\n", __func__, sqlite3_errmsg(db));
    return -1;
  }

  // update record
  if (sqlite3_exec(db, "UPDATE CountList SET count = count + 1 WHERE name == 'boot'", NULL, NULL, NULL) != SQLITE_OK)
  {
    printf("%s[%s]\n", __func__, sqlite3_errmsg(db));
    return -1;
  }

  // get & view record
  RelationData relation_data;
  if (sqlite3_exec(db, "SELECT * FROM CountList", exec_callback, (void *)&relation_data, NULL))
  {
    printf("%s[%s]\n", __func__, sqlite3_errmsg(db));
    return -1;
  }
  relation_data.print();

  printf("Done\n");
  return 0;
}
  • 前回のサンプルとの大きな違いはsqlite3_exec(db, "SELECT * FROM CountList", exec_callback, (void *)&relation_data, NULL)です。
    • 第3引数で結果を受け取るcallback関数の関数ポインタを入れます。
    • 第4引数でcallback関数で受け取る結果保存用の構造体のポインタを入れます。この値がexec_callbackの第1引数で渡されます。
    • 結果のレコード(行)が複数ある時は、レコード数だけexec_callbackが呼ばれます。
RelationDataの実装
/home/ubuntu/ros2_ws/src/ros2_lecture/sqlite_lecture/include/sqlite_lecture/relation_data.h
#include <string>
#include <vector>
#include <map>

class RelationData
{
public:
  RelationData()
  {
  }

  void increment(void)
  {
    line_size_++;
    for (auto &c : data_)
    {
      c.second.push_back("");
    }
  }

  void setAtBack(std::string column_name, std::string value)
  {
    // new
    if (data_.count(column_name) == 0)
    {
      std::vector<std::string> column;
      column.resize(line_size_);
      data_[column_name] = column;
    }

    if (data_[column_name].empty())
    {
      printf("empty %s\n", column_name.c_str());
      return;
    }
    data_[column_name].back() = value;
  }

  std::string get(std::string column_name, size_t line) const
  {
    if (!checkSize())
    {
      return "";
    }
    if (data_.count(column_name) == 0)
    {
      printf("not found %s\n", column_name.c_str());
      return "";
    }
    if (line >= data_.at(column_name).size())
    {
      printf("no data %s %lu (should < %lu)\n", column_name.c_str(), line, data_.at(column_name).size());
      return "";
    }
    return data_.at(column_name)[line];
  }

  size_t size(void) const
  {
    if (!checkSize())
    {
      return 0;
    }
    return line_size_;
  }

  void print(void) const
  {
    if (!checkSize())
    {
      return;
    }
    printf("======table======\n");
    for (auto c : data_)
    {
      printf("[%s]: ", c.first.c_str());
      for (auto v : c.second)
      {
        printf("%s, ", v.c_str());
      }
      printf("\n");
    }
    printf("=================\n");
  }

private:
  bool checkSize(void) const
  {
    bool size_is_ok = true;
    for (auto c : data_)
    {
      if (line_size_ != c.second.size())
      {
        size_is_ok = false;
        printf("%s is %lu (expected %lu)\n", c.first.c_str(), c.second.size(), line_size_);
      }
    }
    return size_is_ok;
  }

  size_t line_size_{0};
  std::map<std::string, std::vector<std::string>> data_;
};

ビルド&実行

ビルド
source /opt/ros/humble/setup.bash
cd ros2_ws
colcon build
実行
$ ros2 run sqlite_lecture sample2 
Start
======table======
[count]: 1, 
[name]: boot, 
=================
Done
$ ros2 run sqlite_lecture sample2 
Start
======table======
[count]: 2, 
[name]: boot, 
=================
Done
$ ros2 run sqlite_lecture sample2 
Start
======table======
[count]: 3, 
[name]: boot, 
=================
Done

実行する毎にbootの値がインクリメントされるのが分かります。

ステートメントでSQLを使う

データのINSERTではSQL命令中に入力するデータの値を入れる必要があります。文字列操作をしてこれらの値が入ったSQL文をユーザーが作成してもよいのですが、SQLiteのAPIにはステートメントという機能があります。これを使ってデータをinsertしてみます。

ソースコード

sqlite_lecture/src/sample3/main.cpp
#include <sqlite3.h>
#include <sqlite_lecture/relation_data.h>

int exec_callback(void *data, int argc, char **argv, char **azColName)
{
  RelationData *relation_data = (RelationData *)data;
  relation_data->increment();
  for (int i = 0; i < argc; i++)
  {
    relation_data->setAtBack(azColName[i], argv[i]);
  }
  return 0;
};

int main(int argc, char *argv[])
{
  printf("Start\n");

  sqlite3 *db = NULL;
  if (sqlite3_open("data.db", &db) != SQLITE_OK)
  {
    printf("%s[%s]\n", __func__, sqlite3_errmsg(db));
    return -1;
  }

  // create Table
  if (sqlite3_exec(db, "CREATE TABLE IF NOT EXISTS PositionList (name PRIMARY KEY, x, y);", NULL, NULL, NULL) != SQLITE_OK)
  {
    printf("%s[%s]\n", __func__, sqlite3_errmsg(db));
    return -1;
  }

  // insert record with statement
  char sql_raw[256] = "INSERT OR IGNORE INTO PositionList VALUES (?, ?, ?);";
  sqlite3_stmt *pStmt = NULL;
  if (sqlite3_prepare_v2(db, sql_raw, 256, &pStmt, NULL) != SQLITE_OK)
  {
    printf("%s[%s]\n", __func__, sqlite3_errmsg(db));
    return -1;
  }

  // 1st data
  sqlite3_bind_text(pStmt, 1, "pos1", -1, SQLITE_TRANSIENT);
  sqlite3_bind_double(pStmt, 2, 1.1);
  sqlite3_bind_double(pStmt, 3, 2.2);
  while (SQLITE_DONE != sqlite3_step(pStmt))
  {
  }
  sqlite3_reset(pStmt);

  // 2nd data
  sqlite3_bind_text(pStmt, 1, "pos2", -1, SQLITE_TRANSIENT);
  sqlite3_bind_double(pStmt, 2, 5.1);
  sqlite3_bind_double(pStmt, 3, 6.2);
  while (SQLITE_DONE != sqlite3_step(pStmt))
  {
  }
  sqlite3_finalize(pStmt);

  // get & view record
  RelationData relation_data;
  if (sqlite3_exec(db, "SELECT * FROM PositionList", exec_callback, (void *)&relation_data, NULL))
  {
    printf("%s[%s]\n", __func__, sqlite3_errmsg(db));
    return -1;
  }
  relation_data.print();

  printf("Done\n");
  return 0;
}
  • sqlite3_prepare_v2でステートメントを準備します。この時に使うSQL文は一部が?になっています。
  • sqlite3_bind_double_text?に文字列を代入します。
    • 第2引数は何番目の?に代入するかというものです。1から数え始めることに注意です。
    • 第4引数は文字数ですが-1を入れるとnull終端までを見ます。
    • 第5引数のSQLITE_TRANSIENTは内部的に文字列をコピーするというオプションで、これを指定しておくのが無難です。
  • sqlite3_bind_doubleでは数字を?に代入します。
  • sqlite3_stepではステートメントのSQLを実行します。
  • ステートメントを使いまわすときはsqlite3_resetでbindの結果をリセットしてからもう一度bindします。
  • ステートメントを使い終わったらsqlite3_finalizeでクリアします。

ビルド&実行

ビルド
source /opt/ros/humble/setup.bash
cd ros2_ws
colcon build
実行
ros2 run sqlite_lecture sample3 
Start
======table======
[name]: pos1, pos2, 
[x]: 1.1, 5.1, 
[y]: 2.2, 6.2, 
=================
Done

DBに保存したデータが読み出せています。

目次ページへのリンク

ROS2講座の目次へのリンク

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?