5
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

概要

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

Libpq-Fortran — A Modern Fortran Interface for libpq

導入

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

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

  • fortran-sqlite3
    Modern interface bindings to SQLite 3 in pure Fortran 2018.

  • FLIBS
    Fortran 90 modules that include non-standard wrapper routines around SQLite

  • libGPF
    General Purpose Fortran collection, includes SQLite 3 bindings.

  • sqliteff
    SQLite for Fortran 2003, a thin C wrapper around the SQLite library.

SQLite - Programming in Modern Fortran, written by Philipp Engel

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

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

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パッケージマネージャーfpm)が必要です。これはGitHubからダウンロードすることができます。加えて筆者の別ライブラリのuint-fortranが必要ですが、これはfpmが自動的に依存関係を処理してくれます。

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

ビルドする

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

$ 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なので、コマンドは以下のようになります。

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

Libpq-Fortranを試す

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

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

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

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

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

=== 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の下に少なくともpostgrestemplate1template0の3つが表示されれば成功です。これでFortranで記述されたコードを使って、データベースサーバーにアクセスすることを達成できました。次のセクションでは今使ったコードについて詳しく見ていきます。

program demo

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

宣言部

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

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ポインタ変数を宣言しています。

実行部

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

   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つの可変長文字列変数にコピーしています。

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

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

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

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

   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オブジェクトから、結果のデータを取り出す部分に移ります。

    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

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

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

   call PQclear(res)
   call PQfinish(conn)

実行結果

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

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

ここで少なくとも(PostgreSQLサーバーのデフォルトの設定では)postgrestemplate1template0の3つのデータベース名が表示されていれば成功です。

おわりに

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

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

謝辞

参考文献

  1. SQLite - Programming in Modern Fortran, written by Philipp Engel

補遺

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

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
5
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?