我想要在Fortran中使用数据库!

简而言之

PostgreSQL具有用于前端的C库libpq。借助现代Fortran的C互操作功能,我们创建了一个Fortran封装程序Libpq-Fortran,并介绍其用法。

Libpq-Fortran — Libpq的现代Fortran接口

引入

在关系数据库管理系统中,有多种实现方法。例如MySQL、PostgreSQL、IBM Db2等都是我们能想到的。此外,还有适用于轻量级应用的SQLite。

根据Philipp Engel先生的说法(参考文献1),有几个Fortran模块与SQLite相关。以下有关这四个库的总结。

Fortran-SQLite3是以纯Fortran 2018编写的现代接口绑定到SQLite 3的工具。

FLIBS是Fortran 90模块,包含围绕SQLite的非标准包装例程。

libGPF是一个通用的Fortran集合,包括SQLite 3的绑定。

sqliteff是Fortran 2003的SQLite版本,它是SQLite库的一个薄C包装。

《SQLite – 使用现代Fortran编程》是由Philipp Engel撰写的书籍。

然而,这些只是数据库的“库”,并不能提供访问数据库“服务器”的手段。虽然引入的难度可能会比较高,但如果需要处理相当大规模的数据,也会有一些情况需要在服务器上进行管理。

因此,我决定开发一个作为PostgreSQL数据库服务器客户端接口的Fortran模块。我将它在GitHub上以”Libpq-Fortran”的名称公开(采用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数据库服务器。这个服务器必须可通过本地计算机或本地网络访问。请准备一个不会有数据丢失问题的数据库,用于进行此软件的测试。

建造

首先,从bash或powershell等终端执行以下命令来构建Libpq-Fortran。

$ 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下面至少显示出postgres、template1、template0这三个名称,那么就表示成功了。在使用Fortran编写的代码的帮助下,我们成功实现了对数据库服务器的访问。接下来的部分将详细介绍我们所使用的代码。

演示程序

在这个部分,我们将详细说明上面例子中使用的demo.f90程序。整个程序可以在本文的附录或GitHub上查看。

宣言部 bù)

這個程式的宣告語句如下:

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中,通过声明使用该模块,可以从Fortran中使用libpq的过程。

接下来有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连接字符串。

在下一行执行的是连接到数据库的操作。

   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

首先,我们将SQL查询语句写入字符串变量query。然后,将query和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

首先调用函数PQntuples和PQnfields,显示res对象的结果行数和列数。然后,根据行数循环,使用函数PQgetvalue提取第i行第0列的数据并显示出来。请注意,这里使用了C语言规则中的基于0的索引。

最后调用PQclear函数和PQfinish函数释放结果对象,并结束连接。

   call PQclear(res)
   call PQfinish(conn)

执行结果

按照这样的方式编写的程序将在执行时,在终端上输出如下所示的显示,就像在上面执行的一样。

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

如果在这里至少显示出(在PostgreSQL服务器的默认配置下)postgres、template1、template0这三个数据库名称,那么就表示成功了。

最后

我们发布了一个用于访问PostgreSQL官方库libpq的Fortran模块Libpq-Fortran,并介绍了如何首次使用它。

由于Libpq-Fortran只是直接封装了libpq,因此在实际管理应用程序数据时需要有一定的SQL和数据库设计知识。特别是关于使用PostgreSQL数据库来实现数值计算数据保存的方法,因篇幅过长,本文将在另一篇文章中详细介绍。

感谢

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

このライブラリを作ったのは、2023年9月10日のモダンFortran勉強会.forでのディスカッションがきっかけの一つです。

引用文献

    SQLite -使用现代Fortran进行编程,由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
广告
将在 10 秒后关闭
bannerAds