Postgres の COPY コマンドでは, libpq が使っているネットワーク接続に対して読み込み, あるいは書き込みを選ぶことができるようになっています. そこでこのネットワーク接続に直接アクセスするための関数が必要になります. もちろんアプリケーションもこの機能によって恩恵を受けるでしょう.
これらの関数は,PQexec または PQgetResult から, PGRES_COPY_OUT か PGRES_COPY_IN 結果オブジェクトを受け取った後にのみ, 実行するべきです.
PQgetline 改行で終端する文字列(バックエンドから送信されたもの)を 長さ length のバッファ string に読み込みます.
int PQgetline(PGconn *conn,
char *string,
int length)
fgets(3) と同様,このルーチンはバッファに最大 (length-1)
個の文字をコピーしますが,終端の改行が NULL 文字に置き換えられる点では
gets(3) に似ています.
PQgetline はファイル終端では EOF を,行全体が読み込まれれば 0 を返します.
そしてまだ終端の改行が読み込まれていないうちにバッファがいっぱいに
なってしまった場合は 1 を返します.
アプリケーションは新しく読み込んだ行が,
"\." という二文字であるかどうか確認しなければなりません.この二文字は,
COPY コマンドの結果をバックエンドサーバが送信し終えたことを示すものです.
アプリケーションには,仮に (length-1) 文字より長い行を受け取るようなことがあっても,
間違いなく最終行("\.")を認識するような配慮が必要です.
(またたとえば長いデータの行の切れっぱしを,最終行と取りちがえないようにもしてください)
プログラム
../src/bin/psql/psql.c
が,COPY プロトコルを正しく処理するルーチンを持っています.PQgetlineAsync 改行で終端する行(バックエンドから送信されたもの)を, ブロッキングなしでバッファに読み込みます.
int PQgetlineAsync(PGconn *conn,
char *buffer,
int bufsize)
PQgetline と似ていますが,このルーチンは COPY コマンドのデータを非同期的に,
つまりブロッキングなしで読み出さなければならないアプリケーションで使います.
COPY コマンドを発行し,そして PGRES_COPY_OUT 応答を受け取ったら,
アプリケーションはデータ終了の合図を受け取るまで
PQconsumeInput と PQgetlineAsync を呼び出し続けます.
PQgetline と違い,このルーチンはデータ終了の検出に対して責任を持ちます.
PQgetlineAsync が個々の呼び出しでデータを返すのは次の場合です.
ひとつはデータ行を,改行終端の完全な形で
libpq の入力バッファから取り出すことができる場合.
もうひとつは,呼び出し側が用意したバッファでは受信データが長すぎて,
収まりきらない場合です.
一方,このような長い行の残り部分が届かない限りは,何も返しません.
このルーチンは,コピーされるデータの終端を示すマークを認識すると -1 を,
また何もデータがなければ 0 を,そしてデータを返す場合はそのバイト数を正の値で返します.
もし -1 が返されたら,呼び出し側は次に PQendcopy を呼び出さなければなりません.
それから通常の処理に戻ります.
返されるデータは改行文字までで,可能であれば行全体を一度に返します.
しかし呼び出し側が準備したバッファが少なすぎ,
バックエンドから送られてくる行を保持しておくことができない場合には,
部分ごとのデータを返します.
これは最後の1バイトが '\n' かどうかを確認すれば検出できます.
なお,返される文字列は NULL 終端にはなっていません.
(終端 NULL をあとから付け加えるのであれば,
( 実際のバッファサイズ - 1 ) を bufsize として渡すようにしてください)PQputline NULL 終端の文字列をバックエンドサーバに送信します. 正常終了なら 0 を返し,文字列を送信できなかった場合は EOF を返します.
int PQputline(PGconn *conn,
char *string);
アプリケーションはデータ送信の完了をバックエンドに示すために,
最終行で "\." の二文字を明示的に送信しなければなりません.PQputnbytes NULL 終端されていない文字列をバックエンドサーバに送信します. 正常終了なら 0 を返し,文字列を送信できなかった場合は EOF を返します.
int PQputnbytes(PGconn *conn,
const char *buffer,
int nbytes);
送信するバイト数を直接指定するので,
データバッファが NULL 終端である必要がない点を除けば,
これは PQputline と全く同様のものです.PQendcopy バックエンドと同期します. この関数はバックエンドがコピーを完了するのを待ちます. この関数は PQputline を使ったバックエンドへの文字列送信が完了した時点, あるいは PGgetline を使ったバックエンドからの文字列受信が完了した時点の いずれでも呼び出すべきです.さもないと, バックエンドがフロントエンドとの「同期ずれ」を起こしてしまうかもしれません. この関数から戻った時点で,バックエンドの次の問い合わせを受ける準備が整います. 同期が成功すれば 0 を,そうでなければ 0 以外の値を返します.
int PQendcopy(PGconn *conn);
一例です :
PQexec(conn, "create table foo (a int4, b char(16), d float8)"); PQexec(conn, "copy foo from stdin"); PQputline(conn, "3\thello world\t4.5\n"); PQputline(conn,"4\tgoodbye world\t7.11\n"); ... PQputline(conn,"\\.\n"); PQendcopy(conn);
PQgetResult を使うとき,アプリケーションは PQgetline を繰り返し呼び出して PGRES_COPY_OUT に応答し,最終行を見つけたら続いて PQendcopy を呼び出します. それから,PQgetResult が NULL を返すまで PQgetResult のループに戻っておくべきです. 同じように PGRES_COPY_IN は連続した PQputline で処理し, そして PQendcopy でしめくくった後に PQgetResult のループに戻ります. このようにすることで,ひとつづきの SQL コマンド群に含めたコピーイン/ アウトコマンドを確実に,また正しく実行できるはずです. 比較的古いアプリケーションでは,コピーイン/アウトを PQexec で実行し,PQendcopy の実行でトランザクションは完了する, と想定していることがよくあります. これは文字列中の SQL コマンドが唯一コピーイン/アウトであったときにのみ正しく動作します.