環境
この記事は以下の環境で動いています。
項目 | 値 |
---|---|
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に保存したデータが読み出せています。