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