Fortranでもデータベースしたい！

## 概要

PostgreSQLにはフロントエンドとしてCライブラリの`libpq`があります。モダンFortranのC相互運用機能を使って、Fortranラッパーの`Libpq-Fortran`を作ったのでこれの使い方を紹介します。

[Libpq-Fortran — A Modern Fortran Interface for libpq](https://github.com/ShinobuAmasaki/libpq-fortran)

## 導入

関係データベース管理システムには色々な実装があります。例えばMySQL、PostgreSQL、IBM Db2などが思い浮かぶでしょう。他には軽量向けにSQLiteがあります。

SQLiteについてはいくつかFortranモジュールがあるようです。Philipp Engel氏によれば（参考文献1）、以下の4つのライブラリについてまとめられています。

> - [fortran-sqlite3](https://github.com/interkosmos/fortran-sqlite3)
>   Modern interface bindings to SQLite 3 in pure Fortran   2018.
>
> - [FLIBS](http://flibs.sourceforge.net/)
>   Fortran 90 modules that include non-standard wrapper routines   around SQLite
>
> - [libGPF](http://urbanjost.altervista.org/LIBRARY/libGPF/download/tmp/html/download.html)
>   General Purpose Fortran collection, includes SQLite 3   bindings.
>
> - [sqliteff](https://gitlab.com/everythingfunctional/sqliteff)
>   SQLite for Fortran 2003, a thin C wrapper around the SQLite   library.
>
> [SQLite - Programming in Modern Fortran](https://cyber.dabamos.de/programming/modernfortran/sqlite.html), written by Philipp Engel

しかしながら、これらはデータベースの「ライブラリ」であって、いずれもデータベース「サーバー」にアクセスする手段を提供するものではありません。導入のハードルは少々高いですが、それなりに大規模なデータを扱うならサーバーで管理したい場面もあると思います。

そのため、筆者はPostgreSQLデータベースサーバーへのクライアントインターフェースとして機能するFortranモジュールを開発することにしました。これは["Libpq-Fortran"という名前でGitHubで公開しています（MITライセンス）](https://github.com/ShinobuAmasaki/libpq-fortran)。

Libpq-FortranはFortranのC相互運用性の機能を使用して、PostgreSQLの公式Cライブラリlibpqへアクセスすることができます。このソフトウェアのドキュメントはまだ準備中ですが、本稿ではこれの最初の使い方について述べたいと思います。

## 依存関係ソフトウェア

このセクションではLibpq-Fortranが依存しているソフトウェアについて述べます。

ビルドに必要なソフトウェアは以下の通りです。

- libpq
- Fortranコンパイラ
- Fortranパッケージマネージャー（`fpm`）

まず、ローカルマシンのシステムにインストールされたlibpqライブラリが必要です。これはWindowsではPostgreSQLをウィザードでインストールしたときにインストールされます。具体的にはPostgreSQLバージョン15の場合、`C:\Program Files\PostgreSQL\15\include\libpq-fe.h`というファイルが存在していれば大丈夫です。Ubuntuの場合は`sudo apt install libpq-dev`のコマンドを実行すればインストールすることができます。

次にFortranコンパイラが必要になります。現在、Libpq-Fortranは以下のコンパイラによるビルドをサポートしています。

- GNU Compiler Collection: `gfortran`
- Intel oneAPI HPC toolkit: Fortran Compiler `ifx`, Fortran Compiler Classic `ifort`

更に[Fortranパッケージマネージャー](https://fpm.fortran-lang.org/ja/index.html)（`fpm`）が必要です。これはGitHubからダウンロードすることができます。加えて筆者の別ライブラリの`uint-fortran`が必要ですが、これは`fpm`が自動的に依存関係を処理してくれます。

最後に接続先のPostgreSQLデータベースサーバーが必要です。これはローカルマシンかローカルネット経由でアクセス可能である必要があります。**このソフトウェアのテストに使うにはデータを失っても構わないデータベースを用意して使用してください**。

## ビルドする

手始めにLibpq-Fortranをビルドするには、bashやpowershellなどのターミナルから以下のコマンドを実行します。

```shell
$ git clone https://github.com/shinobuamasaki/libpq-fortran
$ cd libpq-fortran
$ fpm build
```

`fpm build`を実行するときに、"libpq-fe.h"ファイルのディレクトリパスをコンパイラに教える必要がある場合があります。この場合は、環境変数`FPM_CFLAGS`またはビルドオプション`--c-flag`に`-I`の後に続けてパスを記述します。

例えばUbuntuの場合、当該ディレクトリはおそらく`/usr/include/postgresql`なので、コマンドは以下のようになります。

```shell
$ export FPM_CFLAGS="-I/usr/include/postgresql"
$ fpm build
または
$ fpm build --c-flag "-I/usr/include/postgresql"
```

## Libpq-Fortranを試す

リポジトリの`example`ディレクトリには、対話的にPostgreSQLサーバーにアクセスして、サーバーのデータベース一覧を取得するプログラムが含まれています。

これを実行するには、`fpm run`コマンドを以下のように実行します。

```shell
$ fpm run demo --example
demo.f90                               done.
demo                                   done.
[100%] Project compiled successfully.
=== INPUT Database Information ===
=== type "q" for quit          ===
Hostname:
```

アクセスするサーバーについての情報入力を求める`Hostname:`のプロンプトが表示されれば成功です。

これに次の例のように情報を入力すれば、データベースへの接続が確立されて結果を取り出すことができます。（ホスト名、データベース名、ユーザー名、パスワードは各自の環境に合わせて書き換えてください）。

```shell
=== INPUT Database Information ===
=== type "q" for quit          ===
Hostname: localhost
dbname: postgres
user: shinobu
password: xxxyyyzzz
=== END INPUT ===
=== Query Result===
tuples, fields: 3  1
=== Available database names ===
postgres
template1
template0
```

`Available database names`の下に少なくとも`postgres`、`template1`、`template0`の3つが表示されれば成功です。これでFortranで記述されたコードを使って、データベースサーバーにアクセスすることを達成できました。次のセクションでは今使ったコードについて詳しく見ていきます。

## `program demo`

このセクションでは上の例で用いた`demo.f90`のプログラムについて詳細に述べていきます。プログラムの全体は、本稿の補遺またはGitHubで見ることができます。

### 宣言部

このプログラムの宣言文は以下の通りです。

```fortran
program demo
   use :: libpq
   use, intrinsic :: iso_c_binding
   use, intrinsic :: iso_fortran_env, only:stdout=>output_unit, stdin=>input_unit

   type(c_ptr) :: conn, res　
   character(:, kind=c_char), allocatable :: query, conninfo
   integer :: i, port
   character(256) :: str, host, dbname, user, password
```

ここで、`use :: libpq`の宣言があることに注目してください。このモジュールはLibpq-Fortranの中核を担うモジュールです。Libpq-Fortranでは、このモジュールを`use`宣言することによってlibpqの手続をFortranから利用できるようになります。

次に`use, intrinsic :: iso_c_binding`の宣言があります。これを宣言しているのは、このソフトウェアがCライブラリの直接的なインターフェースなので、関数の戻り値としてオブジェクトのポインタを受け取ってそれをリレーしていく、というCスタイルのプログラミングが必要になるためです。`type(c_ptr) :: conn, res`では、そのスタイルでプログラムを記述するために必要なCポインタ変数を宣言しています。

### 実行部

次に実行文について見ていきます。

```shell
   print *, '=== INPUT Database Information ==='
   print *, '=== type "q" for quit          ==='
   write (stdout, '(a)', advance='no') "Hostname: "
   read  (stdin, *) host
   if (host == 'q') stop

   port = 5432

   write (stdout, '(a)', advance='no') "dbname: "
   read  (stdin, *) dbname
   if (dbname == 'q') stop

   write (stdout, '(a)', advance='no') "user: "
   read  (stdin, *) user
   if (user == 'q') stop

   write (stdout, '(a)', advance='no') "password: "
   read  (stdin, *) password
   if (password == 'q') stop
   print *, "=== END INPUT ==="

   write(str, '(a, i0, a)') &
      "host="//trim(adjustl(host))// &
      " port=", port, &
      " dbname="//trim(adjustl(dbname))// &
      " user="//trim(adjustl(user))// &
      " password="//trim(adjustl(password))
      
   conninfo = str
```

この部分は、接続情報の入力を受け持つコードです。ユーザーの情報入力を固定長文字列に受け取って集め、最後にPostgreSQL接続文字列として1つの可変長文字列変数にコピーしています。

その次の実行文では、データベースへの接続を実行しています。

```fortran
   conn = PQconnectdb(conninfo)
   if (PQstatus(conn) /= 0) then
      print *, PQerrorMessage(conn)
      error stop
   end if
```

引数`conninfo`を与えて関数`PQconnectdb`を呼び出し、結果をCポインタ型変数`conn`に代入しています。このポインタは接続情報の識別子としてユーザーが使用しますが、このオブジェクトの内部構造について考える必要はありません。これに続く`if`ブロックはエラー処理です。接続を確立できなかった場合にエラーメッセージを表示してプログラムを終了します。

更にすすむとSQL文を実行するコードが出てきます。

```fortran
   query = "select datname from pg_database;"
   res = PQexec(conn, query)
   if (PQstatus(conn) /= 0 ) then
      print *, PQerrorMessage(conn)
   end if
```

ここではまず、文字列変数`query`にSQL文を書き込みます。その後、これと`conn`を引数に与えて関数`PQexec`を呼び出し、結果をCポインタ変数`res`に格納します。このSQL文の意味するところは「サーバーにある（テーブルの集合という意味での）データベースの一覧からデータベース名を取得する」というものです。これに続く`if`ブロックは、先程と同様にエラーハンドリングです。

そして、`res`オブジェクトから、結果のデータを取り出す部分に移ります。

```fortran
    print *, "=== Query Result==="
    print '(a, i0, 2x, i0)', 'tuples, fields: ', PQntuples(res), PQnfields(res) 
 
    print *, "=== Available database names ==="
    do i = 0, PQntuples(res)-1
       print *, PQgetvalue(res, i, 0)
    end do
```

まず関数`PQntuples`と`PQnfields`を呼び出して、`res`オブジェクトがもつ結果の行数と列数を表示します。次に行数だけループを繰り返して、関数`PQgetvalue`で`i`行0列目のデータを取り出して表示します。ここでC言語のルールに従って0-basedのインデックスが使われていることに注意してください。

最後に`PQclear`関数と`PQfinish`関数を呼び出して結果のオブジェクトを解放し、接続を終了しています。

```fortran
   call PQclear(res)
   call PQfinish(conn)
```

### 実行結果

この通りに書かれたプログラムを実行すると、上で実行したようにターミナルには次のような表示が出力されるでしょう。

```shell
=== Query Result===
tuples, fields: 3  1
=== Available database names ===
postgres
template1
template0
```

ここで少なくとも（PostgreSQLサーバーのデフォルトの設定では）`postgres`、`template1`、 `template0`の3つのデータベース名が表示されていれば成功です。

## おわりに

PostgreSQLの公式ライブラリlibpqへアクセスするためのFortranモジュールLibpq-Fortranを公開し、その最初の使い方について紹介しました。

Libpq-Fortranはlibpqをそのままラップしただけのインターフェースなので、実際にアプリケーションのデータを管理するにはSQLと多少のデータベース設計の知識が必要です。特に数値計算のデータ保存をPostgreSQLデータベースを用いて実現するための方法は、本稿で書くには長くなりすぎるので、別の記事で書きたいと思います。

## 謝辞

- 参考文献1は@cure_honeyさんに教えていただきました。ありがとうございます。

- このライブラリを作ったのは、[2023年9月10日のモダンFortran勉強会.for](https://modern-fortran-local-user-group.connpass.com/event/291881/)でのディスカッションがきっかけの一つです。

## 参考文献

1. [SQLite - Programming in Modern Fortran](https://cyber.dabamos.de/programming/modernfortran/sqlite.html), written by Philipp Engel

## 補遺

`demo.f90`のプログラム全体は次の通りです。

```fortran
program demo
   use :: libpq
   use, intrinsic :: iso_c_binding
   use, intrinsic :: iso_fortran_env, only:stdout=>output_unit, stdin=>input_unit

   type(c_ptr) :: conn, res
   character(:, kind=c_char), allocatable :: sql, conninfo
   integer :: i, port
   character(256) :: str, host, dbname, user, password

   print *, '=== INPUT Database Information ==='
   print *, '=== type "q" for quit          ==='
   
   write (stdout, '(a)', advance='no') "Hostname: "
   read  (stdin, *) host
   if (host == 'q') stop

   port = 5432

   write (stdout, '(a)', advance='no') "dbname: "
   read  (stdin, *) dbname
   if (dbname == 'q') stop

   write (stdout, '(a)', advance='no') "user: "
   read  (stdin, *) user
   if (user == 'q') stop

   write (stdout, '(a)', advance='no') "password: "
   read  (stdin, *) password
   if (password == 'q') stop
   print *, "=== END INPUT ==="

   write(str, '(a, i0, a)') &
      "host="//trim(adjustl(host))// &
      " port=", port, &
      " dbname="//trim(adjustl(dbname))// &
      " user="//trim(adjustl(user))// &
      " password="//trim(adjustl(password))
     
   conninfo = str

   conn = PQconnectdb(conninfo)
   if (PQstatus(conn) /= 0) then
      print *, PQerrorMessage(conn)
      error stop
   end if

   ! The query to retrieve the names of databases within a database cluster is: 
   sql = "select datname from pg_database;"
   res = PQexec(conn, sql)
   if (PQstatus(conn) /= 0 ) then
      print *, PQerrorMessage(conn)
   end if

   print *, "=== Query Result==="
   print '(a, i0, 2x, i0)', 'tuples, fields: ', PQntuples(res), PQnfields(res) 

   print *, "=== Available database names ==="
   do i = 0, PQntuples(res)-1
      print *, PQgetvalue(res, i, 0)
   end do

   call PQclear(res)
   call PQfinish(conn)

end program demo
```