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」の関数を利用することにしました.

Debian パッケージ

使用している主な Debian パッケージを示します.

パッケージ名(バージョン)
mariadb-server (10.11.3)SQL データベースサーバ
g++ (12.2.0)GNU C++ コンパイラ
libmariadb-dev (10.11.3)上記ウェブサイトで公開されているライブラリの開発ファイル.ライブラリも同時にインストールされます

MariadB モニタ上でテスト用データベース atom を作成

ここでは,以下の手順で作業しています.

  1. 原子の半径等を記述した CSV ファイル radius.csv を作成
  2. 空のデータベース atom を作成
  3. atom のなかに,原子の半径等を格納するための空のテーブル radius を作成
  4. CSV ファイル radius.csv をテーブル radius にインポート
  5. 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 ハンドルを解放します.


参考書の検索