"LARGE OBJECTS" INTRO 03/18/94 PostgreSQL PostgreSQL

説明

Postgres においては、データ値はタップルに格納され、個々のタップルはデータページの大きさを知ることができません。データページのサイズが 8192 バイトであるため、データ値のサイズの上限は比較的小さなものになっています。巨大な原子値の記憶領域をサポートするために、Postgres ではラージオブジェクト・インターフェースを提供しています。このインターフェースでは、ラージ型として宣言されたユーザデータへのファイル志向のアクセスを提供します。

この章では Postgres ラージオブジェクト・データの実装と、それらに対するプログラミング的な問い合わせ言語インターフェースについて解説します。

歴史的な特記事項

Postgres 4.2 では元々、Postgres の外部ファイル、Postgresで管理される Unix ファイル、Postgres データベースに格納されるデータという 3 つのラージオブジェクトの実装をサポートしていましたが、これらはユーザを戸惑わせる原因になっています。その結果として我々は、ラージオブジェクトを Postgres におけるPostgres データベースの内部に格納されたデータとしてサポートします。これによりアクセス速度こそ低下しますが、より厳密なデータの統合とタイムトラベルを提供できるようになります。歴史的な理由により、それらは転置ラージオブジェクトと呼ばれます。(この章では転置とラージオブジェクトを同じ意味で使います。)

転置ラージオブジェクト

転置ラージオブジェクトの実装は、ラージオブジェクトを"チャンク" にまで分散し、タップルの中のチャンクはデータベースに格納されます。ランダムアクセスによる読み書きを行っている時は、B-tree インデックスは正しいチャンク番号の高速な検索を保証します。

ラージオブジェクト・インターフェース

ラージオブジェクトにアクセスするために Postgres が提供する機能として、ユーザ定義の関数としてバックエンドにおいて実行されるもの、および \*(LQ インターフェースを使ってアプリケーションの一部として実行されるものの両方を以下で説明します。(Postgres 4.2 に親しんでいるユーザのために:Postgres では、新しい、より筋の通ったインターフェースを提供する関数のセットを持っています。このインターフェースは \*(LQ だけでなく、動的にロードされる C 関数と同じものです。)

Postgres のラージオブジェクト・インターフェースは、open(2) , read(2) , write(2) , lseek(2) , といった Unix ファイルシステム・インターフェースに類似したモデルとなっています。ラージオブジェクトから特定のデータだけを取り出すために、ユーザの関数はこれらルーチンをコールします。たとえば、顔写真を格納しているmugshot (顔写真、手配写真)と呼ばれるラージオブジェクト型があり、またbeard (ひげ)と呼ばれる関数がmugshot データに宣言されているとします。Beard (ひげ)は写真の下 3 分の 1 を見て、もしそこにひげがあればその色を測定します。ラージオブジェクト全体の値はbeard 関数によりバッファリングされる必要はなく、またチェックされる必要もありません。

ラージオブジェクトは動的にロードされた C 関数と、Libpq ライブラリとリンクされたデータベース・クライアントプログラムのいずれからアクセスされてもかまいません。Postgres では、ラージオブジェクトのオープン・読み・書き・クローズ・シークの各機能を提供しています。

ラージオブジェクトの生成

ルーチン

Oid lo_creat(PGconn *conn, int mode)
    
は新しいラージオブジェクトを生成します。mode は新しいオブジェクトの複数の異なった属性を指定するビットマスクです。以下に示されるシンボル化された定数は
/usr/local/pgsql/src/backend/libpq/libpq-fs.h
    
で定義されています。アクセスタイプ(read, write または両方)は INV_READINV_WRITE ビットを OR することで指定します。ラージオブジェクトはアーカイブされていることが必要です。すなわち、その複数世代のバージョンが定期的に特別なアーカイブ・リレーションに移動される場合、その後 INV_ARCHIVE ビットをセットしなければなりません。mask の下位 16 ビットは、ラージオブジェクトが存在する記憶領域の管理番号です。Berkeley 以外のサイトにおいてはこれらは常に0 です。

以下のコマンドは(倒置)ラージオブジェクトを生成します。

inv_oid = lo_creat(INV_READ|INV_WRITE|INV_ARCHIVE);
    

ラージオブジェクトをインポートする

UNIX ファイルをラージオブジェクトとしてインポートするには、

Oid 
lo_import(PGconn *conn, text *filename)
    
をコールします。filename 引数はラージオブジェクトとしてインポートするファイルのUNIX パス名を指定します。

ラージオブジェクトをエクスポートする

ラージオブジェクトを UNIX ファイルにエクスポートするには、

int
lo_export(PGconn *conn, Oid lobjId, text *filename)
    
をコールします。lobjId 引数はエクスポートするラージオブジェクトの oid を指定し、filename はファイルの UNIX パス名を指定します。

既存のラージオブジェクトをオープンする

既存のラージオブジェクトをオープンするには、

int
lo_open(PGconn *conn, Oid lobjId, int mode, ...)
    
をコールします。lobjId 引数にはオープンするラージオブジェクトの oid を指定します。mode ビットには、オブジェクトを読み込みのみ INV_READ ), ( 書き込みのみ INV_WRITE ), ( その両方のどのモードでオープンするかを制御します。

ラージオブジェクトは生成する前にオープンすることはできません。 lo_open は、後に lo_read , lo_write , lo_lseek , lo_tell , lo_close のいずれかを利用するためのラージオブジェクト識別子を返します。

ラージオブジェクトにデータを書き込む

ルーチン

int
lo_write(PGconn *conn, int fd, char *buf, int len)
    
は、buf からlen バイトをラージオブジェクトfd に書き込みます。fd 引数は、前回のからの返り値を指定します。

実際に書き込まれたバイト数が返ります。エラーが起こった場合は負数が返ります。

ラージオブジェクトをシークする

ラージオブジェクトの現在の読み書きの開始位置を変更するには

int
lo_lseek(PGconn *conn, int fd, int offset, int whence)
    
をコールします。このルーチンは、fd で指定されたラージオブジェクトの現在の位置ポインタをoffset で指定された新しい位置に移動します。whence の有効な値は SEEK_SET SEEK_CUR SEEK_END のいずれかです。ラージオブジェクトをクローズする。

ラージオブジェクトをクローズするには

int
lo_close(PGconn *conn, int fd)
    
をコールします。ここで、fdlo_open で返されたラージオブジェクト識別子です。成功するとlo_close 0 を返し、エラーの場合負数を返します。

組み込み型で登録された関数

SQL 問い合わせにおいて役立つ、組み込み型で登録された関数lo_import およびlo_export という関数があります。

これらは以下のようにして使います。

CREATE TABLE イメージ (
        名前            text,
        ラスタ          oid
);

INSERT INTO イメージ (名前, ラスタ) VALUES ('美しいイメージ', lo_import('/etc/motd'));
SELECT lo_export(イメージ.ラスタ, '/tmp/motd') from イメージ WHERE 名前 = '美しいイメージ';

LIBPQ からラージオブジェクトをアクセスする

以下に \*(LP におけるラージオブジェクト・インターフェースの使い方に関するサンプル・プログラムを示します。プログラム中の一部はコメントアウトされていますが、読者に分かりやすいようにそのまま残してあります。

../src/test/examples
    

\*(LP でラージオブジェクト・インターフェースを使うフロントエンド・アプリケーションは、ヘッダファイル "libpq/libpq-fs.h" をインクルードし、 libpq ライブラリをリンクしなければなりません。

サンプル・プログラム

/*-------------------------------------------------------------------------
 *
 * testlo.c--
 *    test using large objects with libpq
 *
 * Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *    $Header: /usr/local/cvsroot/pgsql/src/man/large_objects.3,v 1.6 1998/03/23 15:09:29 momjian Exp $
 *
 *-------------------------------------------------------------------------
 */
#include <stdio.h>
#include libpq-fe.h
#include libpq/libpq-fs.h

#define BUFSIZE 1024
/* * importFile - * ファイル in_filename を、データベースにラージオブジェクト * lobjOid としてインポートする * */ Oid importFile(PGconn *conn, char *filename) { Oid lobjId; int lobj_fd; char buf[BUFSIZE]; int nbytes, tmp; int fd;
/* * 読み込むファイルをオープンする */ fd = open(filename, O_RDONLY, 0666); if (fd < 0) { /* error */ fprintf(stderr, UNIX ファイル \\%s\\ がオープンできない\\n, filename); }
/* * ラージオブジェクトを生成する */ lobjId = lo_creat(conn, INV_READ|INV_WRITE); if (lobjId == 0) { fprintf(stderr, ラージオブジェクトを生成できません); } lobj_fd = lo_open(conn, lobjId, INV_WRITE); /* * UNIX ファイルから読み込み、倒置ファイルに書き出す */ while ((nbytes = read(fd, buf, BUFSIZE)) > 0) { tmp = lo_write(conn, lobj_fd, buf, nbytes); if (tmp < nbytes) { fprintf(stderr, 読み込み中にエラー発生 \\%s\\, filename); } } (void) close(fd); (void) lo_close(conn, lobj_fd);
return lobjId; }
void pickout(PGconn *conn, Oid lobjId, int start, int len) { int lobj_fd; char* buf; int nbytes; int nread;
lobj_fd = lo_open(conn, lobjId, INV_READ); if (lobj_fd < 0) { fprintf(stderr,ラージオブジェクトをオープンできません %d, lobjId); }
lo_lseek(conn, lobj_fd, start, SEEK_SET); buf = malloc(len+1); nread = 0; while (len - nread > 0) { nbytes = lo_read(conn, lobj_fd, buf, len - nread); buf[nbytes] = '\\0'; fprintf(stderr,>>> %s, buf); nread += nbytes; } fprintf(stderr,\\n); lo_close(conn, lobj_fd); }
void overwrite(PGconn *conn, Oid lobjId, int start, int len) { int lobj_fd; char* buf; int nbytes; int nwritten; int i;
lobj_fd = lo_open(conn, lobjId, INV_READ); if (lobj_fd < 0) { fprintf(stderr,ラージオブジェクトをオープンできません %d, lobjId); }
lo_lseek(conn, lobj_fd, start, SEEK_SET); buf = malloc(len+1); for (i=0;i<len;i++) buf[i] = 'X'; buf[i] = '\\0';
nwritten = 0; while (len - nwritten > 0) { nbytes = lo_write(conn, lobj_fd, buf + nwritten, len - nwritten); nwritten += nbytes; } fprintf(stderr,\\n); lo_close(conn, lobj_fd); }

/* * exportFile - * export large object lobjOid to file out_filename * */ void exportFile(PGconn *conn, Oid lobjId, char *filename) { int lobj_fd; char buf[BUFSIZE]; int nbytes, tmp; int fd;
/* * 倒置 オブジェクト を生成する */ lobj_fd = lo_open(conn, lobjId, INV_READ); if (lobj_fd < 0) { fprintf(stderr,ラージオブジェクトをオープンできません %d, lobjId); }
/* * 書き込むファイルをオープンする */ fd = open(filename, O_CREAT|O_WRONLY, 0666); if (fd < 0) { /* error */ filename); fprintf(stderr,Unix ファイルをオープンできません\\%s\\, lobjId); }
/* * Unix ファイルを読み込んで倒置ファイルに書き出す */ while ((nbytes = lo_read(conn, lobj_fd, buf, BUFSIZE)) > 0) { tmp = write(fd, buf, nbytes); if (tmp < nbytes) { fprintf(stderr,書き込み中にエラー発生 \\%s\\, filename); } }
(void) lo_close(conn, lobj_fd); (void) close(fd);
return; }
void exit_nicely(PGconn* conn) { PQfinish(conn); exit(1); }
int main(int argc, char **argv) { char *in_filename, *out_filename; char *database; Oid lobjOid; PGconn *conn; PGresult *res;
if (argc != 4) { fprintf(stderr, 使用法: %s データベース名 入力ファイル 出力ファイル\\n, argv[0]); exit(1); }
database = argv[1]; in_filename = argv[2]; out_filename = argv[3];
/* * コネクションを確立する */ conn = PQsetdb(NULL, NULL, NULL, NULL, database);
/* バックエンドへのコネクションが成功したかをチェック */ if (PQstatus(conn) == CONNECTION_BAD) { fprintf(stderr,データベース '%s' へのコネクション失敗\\n, database); fprintf(stderr,%s,PQerrorMessage(conn)); exit_nicely(conn); } res = PQexec(conn, begin work;); PQclear(res); printf(ファイル \\%s\\ をインポートしています...\\n, in_filename); /* lobjOid = importFile(conn, in_filename); */ lobjOid = lo_import(conn, in_filename); /* printf(\\tas large object %d.\\n, lobjOid);
printf(ラージオブジェクトの 1000-2000 バイト目を 取り出しています\\n); pickout(conn, lobjOid, 1000, 1000);
the large object with X's\\n); printf(ラージオブジェクトの 1000-2000 バイト目を 書き込んでいます\\n); overwrite(conn, lobjOid, 1000, 1000); */
printf(ラージオブジェクトをファイル \\%s\\ に書き込んでいます...\\n, out_filename); /* exportFile(conn, lobjOid, out_filename); */ lo_export(conn, lobjOid,out_filename);
res = PQexec(conn, commit;); PQclear(res); PQfinish(conn); exit(0); }

翻訳者

堀田 倫英 <hotta@net-newbie.com>