20010207 TurboLinuxServerJ6.1 で PostgreSQL の Cプログラム

PostgreSQLではPHPで使用するのはよく見あたるのだけれども、
Cで記述してると直接PostgreSQLにもCで操作した方が効率が上がります。
でもあまり紹介されていないようなのでこれが手がかりになればと思います。

オールインワンインストールしたのだけれども readline-devel が入っていないようなのでインストール

これはインストールCD
mount /mnt/cdrom
rpm -ivh /mnt/cdrom/TurboLinux/RPMS/readline-devel-4.0-6.i386.rpm
umount /mnt/cdrom

ソースリストが欲しいのでインストール
これはソースCD
mount /mnt/cdrom
rpm -ivh /mnt/cdrom/SRPMS/postgresql-6.5.3-7.src.rpm
ついでに gcc も、(Cコンパイラやってたら展開済みかな?)
rpm -ivh /mnt/cdrom/SRPMS/gcc-2.95.2-3.src.rpm
umount /mnt/cdrom
解凍
cd /usr/src/turbo/SOURCES
tar xvzf postgresql-6.5.3.tar.gz
tar xvzf gcc-2.95.2.tar.gz
tar xvzf gcc-chill-2.95.2.tar.gz
tar xvzf gcc-core-2.95.2.tar.gz
tar xvzf gcc-g++-2.95.2.tar.gz
tar xvzf gcc-g77-2.95.2.tar.gz
tar xvzf gcc-java-2.95.2.tar.gz
tar xvzf gcc-objc-2.95.2.tar.gz

移植機器特有コードを記載するヘッダはファイルだけで中は記載しない。
vi /usr/src/turbo/SOURCES/postgresql-6.5.3/src/include/config.h

標準でインクルードするlibpq-fe.h
ln -s /usr/src/turbo/SOURCES/postgresql-6.5.3/src/interfaces/libpq/libpq-fe.h /usr/src/turbo/SOURCES/postgresql-6.5.3/src/include/libpq-fe.h

使用するOSの依存ヘッダは linux
ln -s /usr/src/turbo/SOURCES/postgresql-6.5.3/src/include/port/linux.h /usr/src/turbo/SOURCES/postgresql-6.5.3/src/include/os.h

サンプルに付いてくるプログラムをコンパイルしてみる。
cd /usr/lib/pgsql/test/examples

cc -I /usr/src/turbo/SOURCES/postgresql-6.5.3/src/include -L /usr/src/turbo/SOURCES/postgresql-6.5.3/src/backend/lib/ testlibpq.c -lpq
a.outとしてコンパイル出来たのでOK、ではもう一つ
cc -I /usr/src/turbo/SOURCES/postgresql-6.5.3/src/include -L /usr/src/turbo/SOURCES/postgresql-6.5.3/src/backend/lib/ testlibpq2.c -lpq
コンパイル出来たのでOK、ではもう一つ
cc -I /usr/src/turbo/SOURCES/postgresql-6.5.3/src/include -L /usr/src/turbo/SOURCES/postgresql-6.5.3/src/backend/lib/ testlibpq3.c -lpq
コンパイルできない
parse error before `va_list'
自動でインクルードしてくれない。
では、testlibpq3.c に次の行を追加。
#include <varargs.h>
このファイルに`va_list'は宣言されているから。
再度コンパイルしてOK、では最後の一つ
cc -I /usr/src/turbo/SOURCES/postgresql-6.5.3/src/include -L /usr/src/turbo/SOURCES/postgresql-6.5.3/src/backend/lib/ testlibpq4.c -lpq
コンパイル出来たのでOK。

では testlibpq3.c を雛形に色々やってみようかな。
testlibpq3.c はナマズ?シーラカンス?の本に解説があるので取っつきやすいです。
ちなみに、
cc は普通 gcc でやります。
-I はインクルードファイルのあるところを指定、
-L はリンクファイルのあるところを指定、
-lpq はリンクライブラリ検索をしてくださいのオプションで、
PQxxxxxxで示されるコマンドのリンクライブラリ libpq を探してリンクしてくれる。
-o は最適化

補足:-lpq は -l リンクライブラリ検索を意味し、pqがそのファイルを示すため、
書式は-lfile、リンクするファイルのリストに file を追加する。
存在するlibfile.so (libここに置き換わる.soだからlibpq.soになる)あるいはもし
-static オプションも使われているなら libfile.a を検索する。
前者の場合、生成された実行ファイルあるいは共有ライブラリに、
共有ライブラリ名 libfile.so が記録されているから、
メモリ中に読まれる際、ダイナミックリンカが全ての記録された共有ライブラリをも
プロセスイメージのアドレス空間へマップしているので検索できる。
後者の場合、必要な関数やデータの実体が実行ファイルにコピーされ、
コードの分量を計算する。(後者の意味は気にしたことがない)

環境変数 LD_LIBRARY_PATH をセットし、libpq.so が見つかるようにしてもいいかな。
     # export LD_LIBRARY_PATH=/usr/lib/pgsql???どこだったかな?
とならないためのおまかせ-lpq

私はバイナリデータをあまり扱わないので普通の使い方ですが、
様々なプログラム仕様に対応できるよう基本的に
下記プログラム中のSU_SIZ,ZI_SIZを変更するような形で使います。
本来テーブルを設計仕様に最適化するべきかもしれませんが、
この方が仕様変更などにも強く後々のメンテナンス性移植性に優れます。
有益なプログラムは数ヶ月後にすぐに仕様変更で機能拡張を要求されるので
その時に慌てないよう仕様の倍数個のフィールドを
使わなくとも割り付けておきます。
もしテーブル内フィールドが最適化されていたら、この時最悪化します。
最適化するのは未来に変更が起こらないことがどうしても前提になるのですが
そういったプログラムは進化出来ないので速く使い物にならなくなります。

予めPostgreSQLにデータベース、テーブルを作る。

テーブルを作るSQL文をvi mktb_sampletb.sqlで作っておく。
インデックスキーもあると便利なので作ったが必要でなければいらない。
mktb_sampletb.sql------------------------------------------------------

Create table sampletb (
     keys        varchar(80),
     su001       int4,
     su002       int4,
     su003       int4,
     zi001       text,
     zi002       text,
     zi003       text
);

create unique index sampletb_index on sampletb(keys);

mktb_sampletb.sql------------------------------------------------------

SQL文実行

$ psql testdb < mktb_sampletb.sql

レコードを書き込む例
postw.c------------------------------------------------------
/*
 * postw.c
 */
#include <time.h>
#include <stdio.h>
#include <string.h>
#include <varargs.h>
#include "postgres.h"
#include "libpq-fe.h"

#define TM_SIZ   80
#define SQLSIZ 2048

#define SU_SIZ  3
#define ZI_SIZ  3

static void
exit_nicely(PGconn *conn)
{
        PQfinish(conn);
        exit(1);
}

int
main(int argc,char **argv)
{
        time_t tmbin;           /* 現在の日時(世界標準時) */
        struct tm *tmval;       /* 現在の日時(ローカル時間) */
        char   *fmt;            /* 日付と時刻の出力フォーマット文 */
        char   strnow[TM_SIZ];  /* 出力文を入れるための文字配列 */

        int             j;              /* SQL 生成カウンタ */

        char            i_sql[SQLSIZ];  /* SQL 文字オーバーフローにならないように注意 */

        char            *pghost;                /* 接続するコンピュータバックエンド */
        char            *pgport;                /* 接続するコンピュータのバックエンドポート */
        char            *pgoptions;             /* バックエンドのスタートアップオプション */
        char            *pgtty;                 /* バックエンドのデバッグ用 tty */
        char            *dbName;                /* データベース名 */

        PGconn          *conn;                  /* コネクション・ファイルハンドル */
        PGresult        *res;                   /* 戻り値 */

        char            *i_keys;
        int4            i_su[SU_SIZ];
        char            *i_zi[ZI_SIZ][255];
        char            i_char[255];            /* 一時保管文字列 */

        pghost = NULL;                          /* NULLだと自動で検索して実行しようと試みてくれる */
        pgport = NULL;                          /* NULLだと自動で検索して実行しようと試みてくれる */
        pgoptions = NULL;                       /* NULLだと自動で検索して実行しようと試みてくれる */
        pgtty = NULL;                           /* 普段使わない */

/* データベース名を引数があればそれを代入 */

        if(argc==1)
        {
                dbName = "testdb";
        }
        else
        {
                if(argc==2)
                {
                        dbName = argv[1];
                }
                else
                {
                        fprintf(stderr,"error [dbName]\n");
                        exit(1);
                }
        }

/* 時刻取り扱いのサンプル
        time(&tmbin);
        printf("char *ctime(tlock)                        =%s",ctime(&tmbin));
        printf("char *asctime(struct tm *localtime(tlock))=%s",asctime(localtime(&tmbin)));
        printf("char *asctime(struct tm *gmtime(tlock))   =%s",asctime(gmtime(&tmbin)));
 */

        /* 現在の日時を画面に出しているだけ、意味はない */
        time(&tmbin);   tmval = localtime(&tmbin);

        /* 日付と時刻をいくつかの形式で画面に出力する */
        fmt = "%Y-%m-%d,%H:%M:%S,%A" ;
        strftime(strnow, (size_t)TM_SIZ, fmt, tmval);
        printf("now:<%s>\n", strnow);

/* データベースと接続 */

        conn = PQsetdb(pghost, pgport, pgoptions, pgtty, dbName);

/* データベースと接続が出来なかった場合 */

        if (PQstatus(conn) == CONNECTION_BAD)
        {
                fprintf(stderr, "Connection to database '%s' failed.\n", dbName);
                fprintf(stderr, "%s", PQerrorMessage(conn));
                exit_nicely(conn);
        }

/* insert 文 */

        strcpy( i_sql , "insert into sampletb values ('" ) ;

        /* 第一フィールドはキーフィールドとして使いたくてテーブルに設けたが必要なければいらない、ここでは時刻をキーにしてみた */
        time(&tmbin);   tmval = localtime(&tmbin);
        fmt = "%Y-%m-%d,%H:%M:%S,%A" ;
        strftime(strnow, (size_t)TM_SIZ, fmt, tmval);
        strcat( i_sql , strnow );
        strcat( i_sql , "'" ) ;

        /* 数字フィールド代入、ここは目的に応じて代入する記述になおす */
        for (j = 0; j < SU_SIZ ; j++)
        {
                i_su[j] = j;
                sprintf( i_char , ",%d" , i_su[j] );
                strcat( i_sql , i_char ) ;
        }

        strcat( i_sql , ")" ) ;

        printf("SQL:<%s>\n", i_sql);
        printf("len:<%d>\n", strlen(i_sql));

        /* SQL 実行 */
        res = PQexec(conn, i_sql);
        if (PQresultStatus(res) != PGRES_COMMAND_OK)
        {
                fprintf(stderr, "INSERT command failed\n");
                if (res)
                {
                        PQclear(res);
                }
                exit_nicely(conn);
        }
        PQclear(res);

/* データベースと切断 */

        PQfinish(conn);
}
postw.c------------------------------------------------------

レコードを読み込む例
postr.c------------------------------------------------------
/*
 * postr.c
 */
#include <stdio.h>
#include <string.h>
#include <varargs.h>
#include "postgres.h"
#include "libpq-fe.h"

#define SU_SIZ 3
#define ZI_SIZ 3

static void
exit_nicely(PGconn *conn)
{
        PQfinish(conn);
        exit(1);
}

int
main(int argc,char **argv)
{
        int             i;                      /* 表示縦方向カウンタ */
        int             j;                      /* 表示横方向カウンタ */

        char            *pghost;                /* 接続するコンピュータバックエンド */
        char            *pgport;                /* 接続するコンピュータのバックエンドポート */
        char            *pgoptions;             /* バックエンドのスタートアップオプション */
        char            *pgtty;                 /* バックエンドのデバッグ用 tty */
        char            *dbName;                /* データベース名 */

        PGconn          *conn;                  /* コネクション・ファイルハンドル */
        PGresult        *res;                   /* 戻り値 */

        char            *i_keys;
        int4            i_su[SU_SIZ];
        char            *i_zi[ZI_SIZ][255];
        int4            i_rec_max;

        int             nFields;                /* レコード内の格納フィールド数 */

        pghost = NULL;                          /* NULLだと自動で検索して実行しようと試みてくれる */
        pgport = NULL;                          /* NULLだと自動で検索して実行しようと試みてくれる */
        pgoptions = NULL;                       /* NULLだと自動で検索して実行しようと試みてくれる */
        pgtty = NULL;                           /* 普段使わない */

/* データベース名を引数があればそれを代入 */

        if(argc==1)
        {
                dbName = "testdb";
        }
        else
        {
                if(argc==2)
                {
                        dbName = argv[1];
                }
                else
                {
                        fprintf(stderr,"error [dbName]\n");
                        exit(1);
                }
        }

/* データベースと接続 */

        conn = PQsetdb(pghost, pgport, pgoptions, pgtty, dbName);

/* データベースと接続が出来なかった場合 */

        if (PQstatus(conn) == CONNECTION_BAD)
        {
                fprintf(stderr, "Connection to database '%s' failed.\n", dbName);
                fprintf(stderr, "%s", PQerrorMessage(conn));
                exit_nicely(conn);
        }

/* トランザクション開始、トランザクション内で処理するカーソルを使用などで必要 */

        res = PQexec(conn, "BEGIN");
        if (PQresultStatus(res) != PGRES_COMMAND_OK)
        {
                fprintf(stderr, "BEGIN command failed\n");
                PQclear(res);
                exit_nicely(conn);
        }
        PQclear(res);

/* select 文 */

        res = PQexec(conn, "DECLARE mycursor CURSOR FOR select * from sampletb");
        if (PQresultStatus(res) != PGRES_COMMAND_OK)
        {
                fprintf(stderr, "SELECT command failed\n");
                if (res)
                {
                        PQclear(res);
                }
                exit_nicely(conn);
        }
        PQclear(res);   /* メモリリークしないようにクリア */

/* 実行結果をフェッチ後、画面表示する */

        res = PQexec(conn, "FETCH ALL in mycursor");
        if (res == NULL ||
                PQresultStatus(res) != PGRES_TUPLES_OK)
        {
                fprintf(stderr, "FETCH ALL command didn't return tuples properly\n");
                if (res)
                        PQclear(res);
                exit_nicely(conn);
        }

        /* レコードに存在する格納フィールドの数を求める */

        nFields = PQnfields(res);
        if (nFields != ( 1 + SU_SIZ + ZI_SIZ ))
        {
                fprintf(stderr, "Field size err\n");
                exit_nicely(conn);
        }

        /* まずフィールド属性名(フィールド項目名称)を表示 */

        for (i = 0; i < nFields; i++)
                printf("%-15s", PQfname(res, i));
        printf("\n\n");

       /* 最後のレコードまで読み配列に代入 */

        i_rec_max = PQntuples(res);

        for (i = 0; i < i_rec_max; i++)
        {
                /* キーフィールドを取り込み */
                i_keys = PQgetvalue(res, i, 0);

                /* 数字フィールドを取り込み */
                for (j = 0; j < SU_SIZ ; j++)
                {
                        i_su[j] = atoi(PQgetvalue(res, i, ( j + 1 )));
                }

                /* 文字フィールドを取り込み */
                for (j = 0; j < ZI_SIZ ; j++)
                {
                        i_zi[j][0] = PQgetvalue(res, i, ( j + 1 + SU_SIZ ));
                }
        }
        PQclear(res);   /* メモリリークしないようにクリア */

/* 画面表示 */

        /* インスタンス(レコード、EXCELでいう縦方向)を全部表示 */
        for (i = 0; i < i_rec_max; i++)
        {
                /* フィールド(EXCELでいう横方向)を全部表示 */

                printf("%-15s", i_keys);
                for (j = 0; j < SU_SIZ; j++)
                {
                        printf(",%d", i_su[j]);
                }
                for (j = 0; j < ZI_SIZ; j++)
                {
                        printf(",%-15s", i_zi[j][0]);
                }
                printf("\n");
        }

/* カーソルを閉じる */

        res = PQexec(conn, "CLOSE mycursor");
        PQclear(res);

/* トランザクションを終わる */

        res = PQexec(conn, "END");
        PQclear(res);

/* データベースと切断 */

        PQfinish(conn);
}
postr.c------------------------------------------------------

レコードを削除する例
postd.c------------------------------------------------------
/*
 * postd.c
 */
#include <stdio.h>
#include <string.h>
#include <varargs.h>
#include "postgres.h"
#include "libpq-fe.h"

static void
exit_nicely(PGconn *conn)
{
        PQfinish(conn);
        exit(1);
}

int
main(int argc,char **argv)
{
        char            *pghost;                /* 接続するコンピュータバックエンド */
        char            *pgport;                /* 接続するコンピュータのバックエンドポート */
        char            *pgoptions;             /* バックエンドのスタートアップオプション */
        char            *pgtty;                 /* バックエンドのデバッグ用 tty */
        char            *dbName;                /* データベース名 */

        PGconn          *conn;                  /* コネクション・ファイルハンドル */
        PGresult        *res;                   /* 戻り値 */

        pghost = NULL;                          /* NULLだと自動で検索して実行しようと試みてくれる */
        pgport = NULL;                          /* NULLだと自動で検索して実行しようと試みてくれる */
        pgoptions = NULL;                       /* NULLだと自動で検索して実行しようと試みてくれる */
        pgtty = NULL;                           /* 普段使わない */

/* データベース名を引数があればそれを代入 */

        if(argc==1)
        {
                dbName = "testdb";
        }
        else
        {
                if(argc==2)
                {
                        dbName = argv[1];
                }
                else
                {
                        fprintf(stderr,"error [dbName]\n");
                        exit(1);
                }
        }

/* データベースと接続 */

        conn = PQsetdb(pghost, pgport, pgoptions, pgtty, dbName);

/* データベースと接続が出来なかった場合 */

        if (PQstatus(conn) == CONNECTION_BAD)
        {
                fprintf(stderr, "Connection to database '%s' failed.\n", dbName);
                fprintf(stderr, "%s", PQerrorMessage(conn));
                exit_nicely(conn);
        }

/* delete 文 */

        res = PQexec(conn, "delete from sampletb");
        if (PQresultStatus(res) != PGRES_COMMAND_OK)
        {
                fprintf(stderr, "DELETE command failed\n");
                if (res)
                {
                        PQclear(res);
                }
                exit_nicely(conn);
        }
        PQclear(res);

/* データベースと切断 */

        PQfinish(conn);
}
postd.c------------------------------------------------------

ちなみに、レコードを削除する例のコンパイルは
gcc -I /usr/src/turbo/SOURCES/postgresql-6.5.3/src/include -L /usr/src/turbo/SOURCES/postgresql-6.5.3/src/backend/lib/ postd.c -lpq

one.