MIRACLE
メールサービス申込 ユーザー登録 パートナー情報
お問い合わせ FAQ サイトマップ
MIRACLE LINUXの特長 製品紹介 サービス案内 購入 サポート 技術フォーラム

プロフィール

日本発のリナックス企業、ミラクル・リナックスで奮闘する社員のブログです。

ミラクル関連リンク

採用情報

サイト検索

最近のトラックバック

2008年8月

          1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
31            

« オープンソースで構築する Web フォーラム (1) | メイン | オープンソースで構築する Web フォーラム (3) »

オープンソースで構築する Web フォーラム (2)

オープンソースで構築する Web フォーラム (1) の続きです。

前回「Subject Prefix にシーケンス番号を追加したいが、どうしよう?」でした。
普通に考えると、シーケンス番号を取得するためのテーブルを用意して、SQL で取得すれば良いのでは?と考えます。

じゃあ、先ず何から調べれば良いのか、全てはソースコードの中に〜♪。

M2F が使用するテーブル一覧を調べてみると、"_seq" の suffix が付くテーブルが幾つかあります。例えば、m2f_message_hash_seq テーブル情報を表示させてみると、id カラムのみ、且つ auto_increment と言うオプションが設定されています。このテーブルはシーケンス番号を取得するために使っているテーブルと見て間違いなさそうです。M2F のソースコードをお手本に、このテーブルからシーケンス番号を取得している処理を参考にすれば、実現できそうな見通しが出てきます。

mysql> desc m2f_message_hash_seq;
+-------+------------------+------+-----+---------+----------------+
| Field | Type             | Null | Key | Default | Extra          |
+-------+------------------+------+-----+---------+----------------+
| id    | int(10) unsigned |      | PRI | NULL    | auto_increment |
+-------+------------------+------+-----+---------+----------------+
1 row in set (0.01 sec)

# less -N m2f_mailinglist.php
...
216 $id = $m2f_db->nextId(_M2F_MESSAGE_HASH_TABLE_, TRUE);
...

m2f_mailinglist.php の中で、 nextId() 関数を用いて、シーケンス番号を取得しています。何となく、この nextId() 関数を使えば良いのだろうなと推測できますが、もう少し掘り下げて追いかけてみると、もっと色んなことが分かってきます。nextId() 関数は、M2F で定義されている関数ではなく、PEAR(PHP Extension and Application Repository) と呼ばれる php の拡張ライブラリで提供されています。ML21/30 の場合は php パッケージに、ML40/AXS3 の場合は php-pear パッケージに含まれています。

(ML40の場合)
# rpm -q php-pear
php-pear-5.0.5-8.15AX

PEAR を使用するために、システムのインクルードパスを調べます。(実際は、デフォルトで M2F にバンドルされている PEAR ライブラリを使用するのですが、PEAR/DB クラスライブラリの中身は同じなので、ここでは、/usr/share/pear 配下のファイルを見てみます)

# php -i | grep include_path
include_path => .:/usr/share/pear => .:/usr/share/pear

m2f_common.php を見ると、103行目の require_once で DB.php を読み込んでいます。118行目の DB::connect  でデータベースへ接続します。引数の $type_db に "mysql" がセットされ、DB.php の535行目において、@include_once で DB/mysql.php が読み込まれます。/usr/share/pear/DB 配下を見れば分かりますが、各々の DB 用のライブラリが用意されています。

# less -N m2f_common.php
...
61 // phpBB uses mysql4 dbms when necessary, but PEAR makes no distinction in the db type
62 switch ($type_db) {
63         case 'mysql4':
64         case 'mysql':
65         $type_db = 'mysql';
66         require_once(M2F_ROOT_PATH."db/m2f_schema_mysql.php"); // include schema functions for this db
67         break ;
68
69   case 'postgres':
70   $type_db = 'pgsql';
71         require_once(M2F_ROOT_PATH."db/m2f_schema_pgsql.php"); // include schema functions for this db
72   break;
73 }
...
103 require_once('DB.php');         // PEAR DB layer
...
...
117 // Instatiate database reusable object
118 $m2f_db = DB::connect( "$type_db://$user_db:$pass_db@$host_db/$name_db" );
...

# less -N /usr/share/pear/DB.php
...
518     function &connect($dsn, $options = array())
519     {
520         $dsninfo = DB::parseDSN($dsn);
521         $type = $dsninfo['phptype'];
522
523         if (!is_array($options)) {
524             /*
525              * For backwards compatibility.  $options used to be boolean,
526              * indicating whether the connection should be persistent.
527              */
528             $options = array('persistent' => $options);
529         }
530
531         if (isset($options['debug']) && $options['debug'] >= 2) {
532             // expose php errors with sufficient debug level
533             include_once "DB/${type}.php";
534         } else {
535             @include_once "DB/${type}.php";
536         }
...

# ls /usr/share/pear/DB
common.php  fbsql.php  ifx.php   mssql.php  mysqli.php  odbc.php   sqlite.php   sybase.php
dbase.php   ibase.php  msql.php  mysql.php  oci8.php    pgsql.php  storage.php

DB.php で DB タイプを判別し、各々の DB 毎にライブラリが実装されているため、ユーザは使用する DB タイプを意識することなく(phpBB2/M2F では postgresql と mysql をサポート)、コーディングすることができます。ここで nextId() 関数は、DB/mysql.php を見れば良いことが分かりました。この辺まで来ると、当初の目的を放り出して、興味本位でコードを追いかけてみたくなってきますよね。

いよいよ nextId() 関数に辿り着きました。この関数を使用する上で2つ要点があります。

1点は、587〜588行目の UPDATE 文で id をインクリメントした後、mysql_insert_id() 関数で直近に生成された id 、つまり、直前に UPDATE 文でインクリメントした id を取得しています。従って、もし、手動でシーケンス番号をリセットした場合、リセットした値 +1 のシーケンス番号が取得されることが分かります。

もう1点は、第1引数で指定したテーブルが存在しない、且つ第2引数のブール値に "TRUE" をセットした場合、627行目の createSequence() 関数を呼び、新規にテーブルを作成していることが分かります。つまり、nextId() 関数を呼び出す際、シーケンス番号用のテーブルの存在有無を意識しなくて良いことも分かります。

他にもこんな風にエラー制御しているんだなと参考になります。

  • mysql におけるシーケンス番号の管理は LAST_INSERT_ID を使えば良さそう
  • テーブルにデータが存在しない場合、ロックを取得して REPLACE 文で挿入している
  • getCode() 関数を使えば、PEAR/DB のデバッグに使えそう

# less -N /usr/share/pear/DB/mysql.php
...
568     /**
569      * Returns the next free id in a sequence
570      *
571      * @param string  $seq_name  name of the sequence
572      * @param boolean $ondemand  when true, the seqence is automatically
573      *                            created if it does not exist
574      *
575      * @return int  the next id number in the sequence.
576      *               A DB_Error object on failure.
577      *
578      * @see DB_common::nextID(), DB_common::getSequenceName(),
579      *      DB_mysql::createSequence(), DB_mysql::dropSequence()
580      */
581     function nextId($seq_name, $ondemand = true)
582     {
583         $seqname = $this->getSequenceName($seq_name);
584         do {
585             $repeat = 0;
586             $this->pushErrorHandling(PEAR_ERROR_RETURN);
587             $result = $this->query("UPDATE ${seqname} ".
588                                    'SET id=LAST_INSERT_ID(id+1)');
589             $this->popErrorHandling();
590             if ($result === DB_OK) {
591                 // COMMON CASE
592                 $id = @mysql_insert_id($this->connection);
593                 if ($id != 0) {
594                     return $id;
595                 }
596                 // EMPTY SEQ TABLE
597                 // Sequence table must be empty for some reason, so fill
598                 // it and return 1 and obtain a user-level lock
599                 $result = $this->getOne("SELECT GET_LOCK('${seqname}_lock',10)");
600                 if (DB::isError($result)) {
601                     return $this->raiseError($result);
602                 }
603                 if ($result == 0) {
604                     // Failed to get the lock
605                     return $this->mysqlRaiseError(DB_ERROR_NOT_LOCKED);
606                 }
607
608                 // add the default value
609                 $result = $this->query("REPLACE INTO ${seqname} (id) VALUES (0)");
610                 if (DB::isError($result)) {
611                     return $this->raiseError($result);
612                 }
613
614                 // Release the lock
615                 $result = $this->getOne('SELECT RELEASE_LOCK('
616                                         . "'${seqname}_lock')");
617                 if (DB::isError($result)) {
618                     return $this->raiseError($result);
619                 }
620                 // We know what the result will be, so no need to try again
621                 return 1;
622
623             } elseif ($ondemand && DB::isError($result) &&
624                 $result->getCode() == DB_ERROR_NOSUCHTABLE)
625             {
626                 // ONDEMAND TABLE CREATION
627                 $result = $this->createSequence($seq_name);
628                 if (DB::isError($result)) {
629                     return $this->raiseError($result);
630                 } else {
631                     $repeat = 1;
632                 }
633
634             } elseif (DB::isError($result) &&
635                       $result->getCode() == DB_ERROR_ALREADY_EXISTS)
636             {
637                 // BACKWARDS COMPAT
638                 // see _BCsequence() comment
639                 $result = $this->_BCsequence($seqname);
640                 if (DB::isError($result)) {
641                     return $this->raiseError($result);
642                 }
643                 $repeat = 1;
644             }
645         } while ($repeat);
646
647         return $this->raiseError($result);
648     }
...

PEAR とよく似た名前の PECL(PHP Extension Community Library) と呼ばれる php の拡張モジュールがあります。大雑把な理解ですが、PEAR は php で書かれた拡張ライブラリで、PECL は C 言語で書かれた拡張モジュール(バイナリ)です。PEAR/DB の機能を PECL で提供しようという試みに PDO(PHP Data Objects) と言うのがあります。メリットは、PEAR/DB と比較してパフォーマンスが優れている点です。

例えば、DB/mysql.php の592行目で mysql_insert_id によって、id を取得していますが、この関数は標準で提供されている拡張モジュールに相当します。

# readelf -a  /usr/lib64/php/modules/mysql.so | grep mysql_insert_id
00000010bbe8  001d00000001 R_X86_64_64       0000000000008010 zif_mysql_insert_id + 0
00000010b6d0  002600000007 R_X86_64_JUMP_SLO 0000000000000000 mysql_insert_id + 0
    29: 0000000000008010   193 FUNC    GLOBAL DEFAULT   10 zif_mysql_insert_id
    38: 0000000000000000    15 FUNC    GLOBAL DEFAULT  UND mysql_insert_id

Phpbb_m2f_add_seqnum 最後に試験的に作成したパッチです。[xxx:00001] のように、Subject Prefix の両端を "[", "]" で括り、5桁のシーケンス番号を追加するようにしてみました。本当にきちんと対応しようとすると、設定画面からシーケンス番号の桁数や有無を指定したり、メーリングリストの削除時にそのテーブルを削除する処理も必要になります。nextId() 関数を使うことで DB とのやり取りやエラー制御の処理が省けて、ほんの数行のパッチで済みました。ライブラリを使用することによって、随分、簡単にできてしまうんだなと感心してしまった次第です。また PEAR/DB から php のコーディングやエラー制御の方法も学ぶことができました。コードを読んで、学んで、改変してと言うのがオープンソースゆえの面白さだなと改めて実感しました。

# diff -uNr m2f_mailinglist.php.orig m2f_mailinglist.php
--- m2f_mailinglist.php.orig    2007-12-05 21:02:26.000000000 +0900
+++ m2f_mailinglist.php     2008-01-18 19:03:20.000000000 +0900
@@ -361,7 +362,12 @@
                        if ('' !== $m2f_subject_prefix)
                        {
                                $mail_msg->headers['Subject'] = str_replace($m2f_subject_prefix . ' ' , '' , $mail_msg->headers['Subject']);
-                               $mail_msg->headers['Subject'] = $m2f_subject_prefix . ' ' . $mail_msg->headers['Subject'];
+                               $m2f_subject_prefix_no_ends = preg_match("[\w]", substr($m2f_subject_prefix, 0, 1) . substr($m2f_subject_prefix, -1))
+                                                                ? $m2f_subject_prefix : substr($m2f_subject_prefix, 1, -1);
+                               $seq_table_name = _M2F_MAILINGLIST_TABLE_ . '_' . $m2f_subject_prefix_no_ends;
+                               $seq_num = sprintf('%05d', $m2f_db->nextId($seq_table_name, TRUE));
+                               $m2f_subject_prefix_with_seq = '[' . $m2f_subject_prefix_no_ends . ':' . $seq_num . ']';
+                               $mail_msg->headers['Subject'] = $m2f_subject_prefix_with_seq . ' ' . $mail_msg->headers['Subject'];
                        }

                        if ($edited === TRUE)

今回の環境、並びに phpBB + M2F のインストール方法を次回に書きます。

順序が逆かな(p_-)o

トラックバック

このページのトラックバックURL:
http://www.typepad.jp/t/trackback/4447/11062170

このページへのトラックバック一覧 オープンソースで構築する Web フォーラム (2):

コメント

コメントを投稿

会社情報 採用情報 個人情報保護方針 商標等取り扱い事項 English
Copyright(c)2000-2006 MIRACLE LINUX CORPORATION. All Rights Reserved.