ROS2講座08 SQLite基礎

# 環境

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

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

# 概要
ROS2はパラメーター等データの読み取りは仕組みがあるのですが、不揮発にデータを保存する標準的な仕組みはありません。
ROS2の多くのノード(例：slamのmap情報の保存)ではファイル書き出しをして行っています。しかしプランニングのユーザーデータなど細々した情報が多く、それぞれをファイル書き出しすると管理が大変です。
ここではSQLiteのデータベースを使ってROS2実行中の情報を不揮発に保存することを目指します。sqliteはROS2のrosbagのファイル形式としても使われている形式です。

## SQLite
データベースというとサーバーで動く物を想像しますが、SQLiteはローカルで動くデータベースで、ライブラリの形で提供されます。SQL構文でデータの操作を行うことが出来ます。

# SQLiteをコマンドで使う

## SQliteファイルを作る。

sqlite3をインストールします。

```shell:sqlite3のインストール
sudo apt install sqlite3
```

sqliteを実行することで

```shell:sqlite基本
$ sqlite3 data.db
SQLite version 3.37.2 2022-01-06 13:25:41
Enter ".help" for usage hints.
sqlite> # <- SQLのコマンドを打てる。Ctrl+Dで抜ける
$ ls
data.db
```

* sqlite3コマンドの引数でファイルを指定します。存在しない場合はファイルを生成します。
* `sqlite>`というプロンプトが出るのでここでSQLコマンドを打ち込みます。Ctrl+Dで抜けます。

## table作成、行の挿入、内容の確認

```sql:データ操作
sqlite> CREATE TABLE DataList (name, count);sqlite> INSERT INTO DataList VALUES("hoge",10);

sqlite> SELECT * FROM DataList;
hoge|10

sqlite> INSERT INTO DataList VALUES("fuga",20);

sqlite> SELECT * FROM DataList;
hoge|10
fuga|20
```

* RDBとは、簡単にいえばExcelのような表形式のデータをコマンドで扱えるものです。
* `CREATE TABLE`で新たな表を作れます。かっこの中は列の設定です。多くのSQLデータベースでは型の指定が必須ですが、SQLiteでは型名の指定は不要です。
* `INSERT INTO`ではデータの行を追加します。
* `SELECT`ではデータの中身を見ることが出来ます。

# SQLiteをsqlitebrowserで使う
sqlite3コマンドだけでデータを操作するのはつらいところ、sqlitebrowserを使うとGUIで操作が出来ます。

```shell:sqlitebrowserのインストール
sudo apt install sqlitebrowser 
```

```shell:sqlitebrowserの実行
sqlitebrowser 
```

「Open DataBase」のボタンで先ほどの「data.db」ファイルを選択します。
するとファイルの中身が表示して「Brows Data」タブを開くとデータベースが表示されます。

![sqlitebrowser.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/254442/3a3458c5-a496-5da1-ef88-42418a7d14ed.png)

GUI上の表に入力することで、データを変更することもできます。データは「Write Change」ボタンを押したときにファイルに書き込まれます。

# SQLiteをc++から使う

## ソースコード

```c++:sqlite_lecture/src/sample1/main.cpp
#include <sqlite3.h>
#include <stdio.h>

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

  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;
  }

  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;
  }

  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;
  }

  printf("Done\n");
  return 0;
}
```

* `CREATE TABLE`でテーブルを作成します。
    * `IF NOT EXISTS`はテーブルが無い時だけ作成します。これをつけないと、テーブルがあるときにエラーになります。
    * `PRIMARY KEY`はその行の値がユニークになる制約を加えます。
* `INSERT INTO`はテーブルにレコード（行）を追加します。
    * これだけだと`PRIMARY KEY`の制約があるために2回目以降の実行でエラーになってしまいます。`OR IGNORE`をつけると`PRIMARY KEY`の制約に反する時は無視します。
* `UPDATE`コマンドではname = boolのレコードでcountをインクリメントします。

## ビルド&実行

```shell:ビルド
source /opt/ros/humble/setup.bash
cd ros2_ws
colcon build
```

```shell:実行
$ ros2 run sqlite_lecture sample1
Done
$ ros2 run sqlite_lecture sample1
Done
$ ros2 run sqlite_lecture sample1
Done
$ sqlite3 data.db 
SQLite version 3.37.2 2022-01-06 13:25:41
Enter ".help" for usage hints.
sqlite> SELECT * FROM CountList;
boot|3
```

3回実行すると、bootの値は3になります。


# 目次ページへのリンク
[ROS2講座の目次へのリンク](https://qiita.com/srs/items/5f0d765343ca78f524ed)