PostgreSQLシステムカタログを追加してみる

PostgreSQLの中身の勉強がてら、触っても問題なさそうなところに手を入れて試してみるシリーズその1(続くのかしら?)

PostgreSQLには、表構造やDBオブジェクトの情報を保持するシステムカタログと呼ばれる領域が存在する。
テーブルの各カラムの情報を保持しているのはpg_attributeというシステムカタログで、ここにはattname(=列名)やattnotnull(=非NULL制約)といったカラムのメタ情報が保持される。
ここにattishiddenというbool型の属性を追加して、attishidden属性がtrueである列は select * from XXXX; とやっても、その列が'*'にマッチしないようにしてみる。

まず、システムカタログのフォーマットを変更するには src/include/catalog 以下の各ヘッダファイルを編集する。pg_attributeに対応するのはpg_attribute.hである。

以下のように、pg_attributeシステムカタログのフォーマットを規定する FormData_pg_attribute構造体が定義されている。

CATALOG(pg_attribute,1249) BKI_BOOTSTRAP BKI_WITHOUT_OIDS
{
    Oid       attrelid;  /* OID of relation */
    NameData  attname;   /* name of attribute */
      :
    /* Number of times inherited */
    int4      attinhcount;
} FormData_pg_attribute;

ここにattishiddenというフィールドを追加したいので、以下の2行をattinhcountの上に追加。

    /* Is a hidden column */
    bool    attishidden;

少し下に、これらの各フィールドに対応する定数が定義されているので、attishiddenに対応する箇所を修正してやる。

-#define Natts_pg_attribute             17
+#define Natts_pg_attribute             18 ←合計数
 #define Anum_pg_attribute_attrelid     1
 #define Anum_pg_attribute_attname      2
       :
 #define Anum_pg_attribute_attislocal   16
-#define Anum_pg_attribute_attinhcount  17
+#define Anum_pg_attribute_attishidden  17
+#define Anum_pg_attribute_attinhcount  18

pg_attribute.hでは、ここより下は全てinitdbが使用するテンプレートの作成に使われるデータ定義になっているので、attishiddenに対応する部分に片っ端からfalse又はFと記述していく。数が多いので結構大変。

システムカタログでもう一つやっておかねばならないのが、pg_classカタログの修正。pg_attributeはカラムの情報を保持するが、pg_classはテーブルの情報を保持する。この中に、テーブルが何個のカラムを持つかということを表すrelnattsという属性があるので、pg_attributeのrelnattsを17から18に変更する。これは、attishiddenを追加した分。

-DATA(insert OID = 1249 (  pg_attribute PGNSP 75 PGUID 0 1249 \
      0 0 0 0 0 f f r 17 0 0 0 0 0 f f f f _null_ ));
+DATA(insert OID = 1249 (  pg_attribute PGNSP 75 PGUID 0 1249 \
      0 0 0 0 0 f f r 18 0 0 0 0 0 f f f f _null_ ));

ヘッダファイルの修正はこの2つのファイルのみ。このヘッダファイル情報を元にinitdbはデータベースクラスタの初期化を行う。

一方、PostgreSQL本体には attishidden を解釈するための修正が必要になる。ここでは、以下の2箇所に手を加えた。
・カラム情報(FormData_pg_attribute)の初期化コード

--- postgresql-8.1.4/src/backend/access/common/tupdesc.c
+++ postgresql-8.1.4-kg/src/backend/access/common/tupdesc.c
@@ -426,6 +426,7 @@ TupleDescInitEntry(TupleDesc desc,
        att->atthasdef = false;
        att->attisdropped = false;
        att->attislocal = true;
+       att->attishidden = false;
        att->attinhcount = 0;

        tuple = SearchSysCache(TYPEOID,

・'*'をマッチしたカラムに展開するコード

--- postgresql-8.1.4/src/backend/parser/parse_relation.c
+++ postgresql-8.1.4-kg/src/backend/parser/parse_relation.c
@@ -1330,6 +1330,8 @@ expandTupleDesc(TupleDesc tupdesc, Alias
            }
            continue;
        }
+       if (attr->attishidden)
+           continue;

        if (colnames)
        {

上のコードは、select '*' from xxxx; などとした場合に、'*'をxxxx表に存在する全てのテーブルにマッチさせる箇所だが、pg_attributeシステムカタログの内容を反映したattrのattishiddenメンバがtrueであれば、そのカラムを展開せずに処理を次の列へと移す。

これらの修正を加えたソースコードをビルド&インストール。
initdbでデータベースクラスタを初期化し、pg_ctlでpostmasterを起動してpsqlで接続する。
まずは、pg_attributeシステムカタログの内容を確認

kaigai=# select * from pg_attribute;
 attrelid |   attname    |〜略〜| attishidden | attinhcount
----------+--------------+------+-------------+-------------
    1247  | typname      |・・・|           f |           0
    1247  | typnamespace |・・・|           f |           0
      :   |        :     |      |           : |           :

確かにattishiddenカラムが追加になっている。

次に、テーブルを作成して内容を確認してみる。

kaigai=# create table foo(
kaigai(#     security_context varchar(80),
kaigai(#     id  serial primary key,
kaigai(#     name varchar(64),
kaigai(#     tel varchar(20)
kaigai(# );
CREATE TABLE
kaigai=# insert into foo (security_context, name, tel)
kaigai-# values('system_u:object_r:pgsql_t:s0', 'KaiGai', '090-1234-5678');
INSERT 0 1
kaigai=# insert into foo (security_context, name, tel)
kaigai-# values('system_u:object_r:pgsql_t:s0.c0', 'ymj', '070-8765-4321');
INSERT 0 1
kaigai=# select * from foo;
        security_context         | id |  name  |      tel
---------------------------------+----+--------+---------------
 system_u:object_r:pgsql_t:s0    |  1 | KaiGai | 090-1234-5678
 system_u:object_r:pgsql_t:s0.c0 |  2 | ymj    | 070-8765-4321
(2 rows)

kaigai=#

'*'が全ての列に展開されている。
次に、security_contextカラムのattishidden属性をtrueにする。

kaigai=# update pg_attribute set attishidden = true
kaigai-#     where attname='security_context';
UPDATE 1
kaigai=# select * from foo;
 id |  name  |      tel
----+--------+---------------
  1 | KaiGai | 090-1234-5678
  2 | ymj    | 070-8765-4321
(2 rows)

kaigai=# select *,security_context from foo;
 id |  name  |      tel      |        security_context
----+--------+---------------+---------------------------------
  1 | KaiGai | 090-1234-5678 | system_u:object_r:pgsql_t:s0
  2 | ymj    | 070-8765-4321 | system_u:object_r:pgsql_t:s0.c0
(2 rows)

kaigai=#

このように明示的にsecurity_context行を指定しない限り、 attishidden = true のカラムが表示されることはない。

で、問題は本当にこれで大丈夫なのかどうかということだな…。