C++ による MariaDB の操作
MariaDB Connector/C は,開発されたアプリケーションを MariaDB および MySQL データベースに接続するために使用されるライブラリです.
このページでは,MariaDB Connector/C を使い,C++ プログラムでデータベースを操作するサンプルコードを書いてみます.
練習として,MariaDB サーバに原子のデータを格納したデータベースを作成し,そこから値を取得します.
原子のデータと言っても,ここで使うのは,118 行 × 4 列の単純なテーブルです.
列は,原子番号(整数),元素記号(文字列),共有結合半径(小数),およびファンデルワールス半径(小数)と 4 列です.
行は,各原子に対応します.確認したら,118 行ありました.
インフォメーション
目次(ページ内リンク)
MariadB モニタ上でテスト用データベース atom を作成
C++ プログラムで atom データベースを操作
サンプルコードで使用したデータ構造と API 関数
ウェブサイト
C & C++ Connectors - MariaDB Knowledge Base:MariaDB のサイト内にある,C/C++ で開発されたアプリケーションを MariaDB および MySQL データベースに接続するためのライブラリ
当「C++ による MariaDB の操作」を作成するにあたり,上記「C++ Connectors」ディレクトリで参照したページを列挙しておきます.
- MariaDB Connector/C API Functions:「C++ による MariaDB の操作」では,ここの関数を利用しています
- MariaDB Connector/C API Prepared Statement Functions:ここのソースコードを改変しています
- MariaDB Connector/C Data Structures:関数の戻り値がどんなデータ構造か知らないと使いにくいと思います
このページのレベルではプリペアドステートメントを使うまでもないと考えたので,「MariaDB Connector/C API Functions」の関数を利用することにしました.
Debian パッケージ
使用している主な Debian パッケージを示します.
パッケージ名(バージョン) | 注 |
mariadb-server (10.11.3) | SQL データベースサーバ |
g++ (12.2.0) | GNU C++ コンパイラ |
libmariadb-dev (10.11.3) | 上記ウェブサイトで公開されているライブラリの開発ファイル.ライブラリも同時にインストールされます |
MariadB モニタ上でテスト用データベース atom を作成
ここでは,以下の手順で作業しています.
- 原子の半径等を記述した CSV ファイル radius.csv を作成
- 空のデータベース atom を作成
- atom のなかに,原子の半径等を格納するための空のテーブル radius を作成
- CSV ファイル radius.csv をテーブル radius にインポート
- radius テーブルの確認
CSV ファイルを作成
表計算ソフトを使い,各行に,原子番号(整数),元素記号(文字列),共有結合半径(小数),およびファンデルワールス半径(小数)を記述した CSV ファイル radius.csv を作成しました.
ただし,ファンデルワールス半径が不明な原子があって,値を 0 としています.
(圧縮ファイルを置いておきます:radius.csv.tar.bz2)
最初の 5 行を示します.
1,H,0.32,1.2 2,He,0.46,1.4 3,Li,1.33,1.82 4,Be,1.02,0 5,B,0.85,0
空のデータベースと空のテーブルを作成して CSV ファイルをインポート
MaroaDB モニタ(仮想端末)で作業します.
緑色の部分が入力部分です.
空のデータベース atom を作成します.名前だけで OK です.
MariaDB [(none)]> create database atom;
Query OK, 1 row affected (0.001 sec)
データベース atom を使います.
MariaDB [(none)]> use atom;
Database changed
データベース atom に空のテーブル radius を作成します.
これには仕様も必要です.原子番号,元素名,共有結合半径,ファンデルワールス半径を格納する列名を num,name,cov,vdw としデータ型等も指定します.
MariaDB [atom]> create table radius (
num int not NULL,
name varchar(3) not NULL,
cov float,
vdw float );
Query OK, 0 rows affected (0.009 sec)
テーブル radius に,radius.csv の内容をインポートします.
フィールドがコンマ区切りであることを明示しないとインポートに失敗します.
MariaDB [atom]> load data local infile "/(絶対パスでディレクトリを指定)/radius.csv" into table radius fields terminated by',';
Query OK, 118 rows affected, 472 warnings (0.004 sec)
Records: 118 Deleted: 0 Skipped: 0 Warnings: 472
radius テーブルの確認
インポートに成功したことを確認するために,テーブルを操作してみます.
正しく格納されていることを確認するには,すべてを表示すればよろしい,ということで,
MariaDB [atom]> select * from radius;
ここでは,共有結合半径とファンデルワールス半径が両方格納されている原子を表示してみます.
共有結合半径はすべての原子が格納されています.
ファンデルワールス半径が不明な場合は,0 となっているので,vdw 列が 0 より大きい行を表示します.
MariaDB [atom]> select * from radius where vdw > 0;
下はその出力です.38 原子が,共有結合半径とファンデルワールス半径の両方とも格納されています.
+-----+------+------+------+ | num | name | cov | vdw | +-----+------+------+------+ | 1 | H | 0.32 | 1.2 | | 2 | He | 0.46 | 1.4 | (中略) | 82 | Pb | 1.44 | 2.02 | | 92 | U | 1.7 | 1.86 | +-----+------+------+------+ 38 rows in set (0.001 sec)
C++ プログラムで atom データベースを操作
冒頭で紹介したページを参照して,サンプルコードを作成してみました.
API 関数を赤色で表示しています.
使っている関数が適当かどうかは,実用的なコードを書かないと判らないところがあります.
例えば下のサンプルコードで,バイナリーセーフ関数を使う必要があるのかとか,プリペアドステートメントを使うべきではないかとか.
#include <iostream>
#include <cstring>
#include <vector>
#include <mariadb/mysql.h>
//エラーが出たら,この関数でその内容を表示
void show_mysql_error(MYSQL *mysql) {
std::cout << "Error(" << mysql_errno(mysql) << ")" << ' ';
std::cout << "[" << mysql_sqlstate(mysql) << "]" << ' ';
std::cout << "\"" << mysql_error(mysql) << "\"" << std::endl;
}
int main() {
//MYSQL 構造体の準備と初期化
MYSQL *mysql = mysql_init(NULL);
//データベースサーバへ接続し,mysql ハンドルを得る
if(!mysql_real_connect(mysql, "localhost", "(MariaDB アカウント)", "(MariaDB パスワード)", "atom", 0, "/var/run/mysqld/mysqld.sock", 0)) {
show_mysql_error(mysql);
mysql_close(mysql);
exit(1);
}
//mysql_real_query() 関数は,クエリを発行するバイナリセーフ関数です.クエリの実行結果はバッファされます.
const char *query= "SELECT * FROM radius where vdw > 0";
if(mysql_real_query(mysql, query, strlen(query))) {
show_mysql_error(mysql);
mysql_close(mysql);
exit(1);
}
//mysql_store_result() 関数は,バッファされているクエリの結果セットを返します
MYSQL_RES *result = mysql_store_result(mysql);
if (!result) {
show_mysql_error(mysql);
mysql_close(mysql);
exit(1);
}
//クエリの実行結果を格納するためのベクトル型配列
std::vector<int> num;
std::vector<std::string> name;
std::vector<float> cov;
std::vector<float> vdw;
//mysql_fetch_row() 関数を使い,クエリの実行結果をベクトル型配列に格納
MYSQL_ROW row;
while((row = mysql_fetch_row(result))) {
num.push_back(atoi(row[0]));
name.push_back(row[1]);
cov.push_back(atof(row[2]));
vdw.push_back(atof(row[3]));
}
//データを端末に出力
int size = num.size();
for(int i = 0; i < size; i++) {
std::cout << num[i] << ' ';
std::cout << name[i] << ' ';
std::cout << cov[i] << ' ';
std::cout << vdw[i] << std::endl;
}
mysql_free_result(result);
mysql_close(mysql);
return 0;
}
ビルドと実行
ビルドと実行
上のコードを main.cc というファイル名で保存しました.
ビルドと実行は下記の通りです.ビルドに成功すれば, a.out という実行ファイルが生成します.
ここでは,ソースコードにホスト名,MariaDB アカウント,MariaDB パスワード,およびデータベース名とテーブル名を書き込んだので,
a.out をそのまま実行するだけです.
~$ g++ main.cc -lmariadb
~$ ./a.out
1 H 0.32 1.2 2 He 0.46 1.4 (中略) 82 Pb 1.44 2.02 92 U 1.7 1.86
サンプルコードで使用したデータ構造と API 関数
上記ソースコードの赤字で示した箇所を,引用したページの換骨奪胎を目指して解説してみました.
データベースやクエリの結果を表現する構造体や配列が,関数の引数や戻り地として利用されています.
MYSQL 構造体
接続しているデータベースを表す構造体です.
mysql_init() で初期化され,多くの API 関数で引数として利用され,mysql_close() で解放されます.
サンプルコードでは,
MYSQL *mysql = mysql_init(NULL);
と NULL を引数として初期化しています.
戻り値の MYSQL 構造体へのポインタを,ここでは,mysql ハンドルと称します.
MYSQL_RES 構造体
クエリを実行した後で取得する,データとメタデータ情報を含む結果セットを表す構造体です.
mysql_use_result(),mysql_store_result(),mysql_stmt_result_metadata() 関数の戻り値です.
mysql_free_result() によって解放せねばなりません.
サンプルコードでは,
MYSQL_RES *result = mysql_store_result(mysql);
と取得しています.
引数の mysql は mysql ハンドルです.
MYSQL_ROW 配列
クエリの結果得られるデータ行の,列を指す文字列へのポインタ(C スタイルの文字列)を格納した配列です.
おそらく頻用されるであろう mysql_fetch_row() 関数の戻り値です.
mysql_free_result() で MYSQL_RES * を解放すると,MYSQL_ROW は無効になります.
サンプルコードでは,
while((row = mysql_fetch_row(result)))
としています.
MYSQL *mysql_init(MYSQL *mysql) 関数
MYSQL へのポインタまたは NULL ポインタを渡して,mysql ハンドルを準備し初期化する関数です.
この関数は初期化をするだけで MySQL には接続しません.
この関数呼び出しの後で,mysql_real_connect() を呼び出して接続します.
mysql_init() で割り当てられたメモリは mysql_close() で解放しなければなりません.
サンプルコードでは
MYSQL *mysql = mysql_init(NULL);
と NULL ポインタを渡しています.
MYSQL *mysql_real_connect(MYSQL *mysql, const char *host, const char *user, const char *passwd, const char *db, unsigned int port, const char *unix_socket, unsigned long flags) 関数
初期化された mysql ハンドルへの接続を確立します(== データベース・サーバへ接続します).
戻り値は mysql ハンドルです.エラーが発生した場合は NULL を返します.
サンプルコードには,
mysql_real_connect(mysql, "localhost", "(MariaDB アカウント)", "(MariaDB パスワード)", "atom", 0, "/var/run/mysqld/mysqld.sock", 0)
と書いておきました.(MariaDB アカウント) と (MariaDB パスワード) は,適切な文字列に置き換えてください.
MYSQL_RES *mysql_store_result(MYSQL *mysql) 関数
mysql ハンドルにバッファされている,最後に実行されたクエリの結果セットを返します.
エラーが発生した場合やクエリがデータを返さなかった場合 (INSERT や UPDATE クエリを実行した場合など) には NULL を返します.
mysql_store_result() によって割り当てられたメモリは,関数 mysql_free_result() を呼び出して解放する必要があります.
サンプルコードでは,
MYSQL_RES *result = mysql_store_result(mysql);
としています.
MYSQL_ROW mysql_fetch_row(MYSQL_RES *result) 関数
クエリの結果セット MYSQL_RES * から 1 行のデータを取り出し,それを char * の配列 MYSQL_ROW として返します.
それ以降この関数を呼び出すたびに,結果セット内の次の行が返され,それ以上行がない場合は NULL が返されます.
列が NULL 値を含む場合,対応する char ポインタは NULL に設定されます.
MYSQL_ROW に関連付けられたメモリは, mysql_free_result() 関数を呼び出す際に解放されます.
サンプルコードでは,
MYSQL_ROW row;
while((row = mysql_fetch_row(result))) {
}
としてすべての行を取得しています.
void mysql_free_result(MYSQL_RES *result) 関数
結果セット MYSQL_RES * を解放します.戻り値は void です.
結果セットが不要になったらすぐに mysql_free_result() で解放する必要があります.
void mysql_close(MYSQL *mysql) 関数
mysql_init() で割り当てられたメモリ mysql ハンドルを解放します.
参考書の検索
- Amazon の「コンピュータ・IT」本カテゴリーでの,「データベース」の検索結果です
- Amazon の「コンピュータ・IT」本カテゴリーでの,「SQL」の検索結果です