Open7

SQLite3 を C を書かずに Fortran で使うことを試みる

ねじくぎねじくぎ

どうやら、関数ポインタやダブルポインタと戦う必要がありそうです

ねじくぎねじくぎ

INSERT文のように出力がない文はこのようにして実行できます。

mod_sqlite3.f90
module mod_sqlite3
    use, intrinsic :: iso_c_binding
    implicit none
    
    ! Error codes
    integer, parameter :: SQLITE_OK = 0
    interface
        ! SQLITE_API int sqlite3_open(
        !     const char *filename,
        !     sqlite3 **ppDb
        ! );
        function sqlite3_open(filename, ppDb) bind(c,name="sqlite3_open")
            use, intrinsic :: iso_c_binding
            implicit none
            integer(c_int) :: sqlite3_open
            type(c_ptr), value :: filename, ppDb
        end function

        ! SQLITE_API int sqlite3_exec(
        !     sqlite3*,                                  /* An open database */
        !     const char *sql,                           /* SQL to be evaluated */
        !     int (*callback)(void*,int,char**,char**),  /* Callback function */
        !     void *,                                    /* 1st argument to callback */
        !     char **errmsg                              /* Error msg written here */
        ! );
        function sqlite3_exec(ppDb, sql, callback, arg1, errmsg) bind(c)
            use, intrinsic :: iso_c_binding
            implicit none
            integer(c_int) :: sqlite3_exec
            type(c_ptr), value :: ppDb, sql, arg1, errmsg
            type(c_funptr), value :: callback
        end function sqlite3_exec
    end interface
contains

end module mod_sqlite3
main.f90
program main
    use, intrinsic :: iso_c_binding
    use :: mod_sqlite3
    implicit none
    
    integer :: status
    character(:), allocatable, target :: dbpath, query
    type(c_ptr), target :: conn, errmsg
    dbpath = "test.db" // c_null_char
    query = "INSERT INTO user(name, age) VALUES('Nejikugi', 19);" // c_null_char

    status = sqlite3_open(c_loc(dbpath), c_loc(conn))
    if (status .ne. SQLITE_OK) then
        print*, "Database open error."
        stop -1
    end if

    status = sqlite3_exec(conn, c_loc(query), c_null_funptr ,c_null_ptr, c_loc(errmsg))
end program main
ねじくぎねじくぎ

SELECT文のように出力がある文はこんな感じの引数を持った関数をc_loc_funptr()で渡せばよさそうだけど、row_txtの長さを得るのが大変そう。'\0'までの長さを数えないとだめ?

subroutine print_resp(arg1, col_count, row_txt, col_name) bind(c)
    type(c_ptr), value :: arg1
    integer(c_int), value :: col_count
    type(c_ptr), value :: row_txt, col_name

    ! do something

end subroutine
ねじくぎねじくぎ

ほぼ成功(文字列長さえきちんと処理すれば)
コールバック関数はサブルーチンじゃなくて関数じゃないとだめっぽい(SQLITE_ABORTする)

main.f90
program main
    use, intrinsic :: iso_c_binding
    use :: mod_sqlite3
    implicit none
    
    integer :: status
    character(:), allocatable, target :: dbpath, query
    type(c_ptr), target :: conn, errmsg
    dbpath = "test.db" // c_null_char
    query = "SELECT * FROM user;" // c_null_char

    status = sqlite3_open(c_loc(dbpath), c_loc(conn))
    if (status .ne. SQLITE_OK) then
        print*, "Database open error."
        stop -1
    end if

    status = sqlite3_exec(conn, c_loc(query), c_funloc(print_resp) ,c_null_ptr, c_loc(errmsg))
    print*, status

contains
    function print_resp(arg1, argc, argv, fields) bind(c)
        implicit none
        integer(c_int) :: print_resp
        type(c_ptr), value :: arg1
        integer(c_int), value :: argc
        type(c_ptr), value :: argv, fields
        type(c_ptr), pointer :: ptr_array(:)
        character, pointer :: fstring(:)
        integer i

        print*, "CALL print_resp()"
        ! ↓これはあってそう
        call c_f_pointer(argv, ptr_array, [argc])


        ! ↓ここ、文字列長をちゃんと求めなさい
        do i=1, argc
            call c_f_pointer(ptr_array(i), fstring, [12])
            print *, 'Column ', i, ': ', fstring
        end do
        ! ↓これがないとSQLITE_ABORTになります
        print_resp = 0
        return
    end function
end program main
ねじくぎねじくぎ

文字列長を求めるところが面倒なら strlen() を持ってくるのもあり

ねじくぎねじくぎ

↑これ、Fortran 2003 (-std=f2003)でコンパイルできません。