WarningとErrorの出力方法

Warningの出力方法

sql/sql_error.hにMYSQL_ERRORクラスとpush_warning関数が定義されてます。

class MYSQL_ERROR: public Sql_alloc
{
public:
  enum enum_warning_level
  { WARN_LEVEL_NOTE, WARN_LEVEL_WARN, WARN_LEVEL_ERROR, WARN_LEVEL_END};

  uint code;
  enum_warning_level level;
  char *msg;
  
  MYSQL_ERROR(THD *thd, uint code_arg, enum_warning_level level_arg,
              const char *msg_arg)
    :code(code_arg), level(level_arg)
  {
    if (msg_arg)
      set_msg(thd, msg_arg);
  }
  void set_msg(THD *thd, const char *msg_arg);
};

MYSQL_ERROR *push_warning(THD *thd, MYSQL_ERROR::enum_warning_level level,
                          uint code, const char *msg);

warningを返すにはこれらのクラスと関数を使います。

レベルは3つあるようです。上記の定義だと"WARN_LEVEL_END"もenumに追加してありますが、これはenumの終端を表すために定義されているものだと思われます。

  • WARN_LEVEL_NOTE
  • WARN_LEVEL_WARN
  • WARN_LEVEL_ERROR

このレベル指定は、show warnings等を実行した際に使用されます。

push_warning関数はsql/sql_error.ccを見たところ以下のな作りになっていました。

  • OPTION_SQL_NOTEフラグが立っていない限り、WARN_LEVEL_NOTEのものは無視(nullを返しておしまい)。
  • strict modeが有効な場合、WARN_LEVEL_WARNをWARN_LEVEL_ERRORに昇格させる。
  • thd->warn_listの長さがmax_error_countに達している場合を除き、引数を用いてMYSQL_ERRORオブジェクトをnewしてthd->warn_listに追加。
  • thd->warn_countとthd->total_warn_countをインクリメント
  • newしたMYSQL_ERRORオブジェクトを返す。(生成してない場合にはnullを返す。)

push_warning関数の引数codeですが、これはinclude/mysqld_error.hに定義されているマクロを使います。

#define ER_ERROR_FIRST 1000
#define ER_HASHCHK 1000
#define ER_NISAMCHK 1001
#define ER_NO 1002
#define ER_YES 1003
...

エラーコードと返されるエラーメッセージの情報は以下のリファレンスマニュアルに記述されています。

ただし、実際に返されるエラーメッセージ(警告メッセージ)はpush_warning関数の引数msgによって決まるので、注意しましょう。例えば引数msgに"hogehoge"を渡すとどんなエラーコードであってもメッセージは"hogehoge"になります。

上記リファレンスマニュアルの仕様に基づいたエラーメッセージを返すには、ERマクロを使用します。ERマクロはsql/unireg.hで以下のように定義されているマクロです。(clientではまた別のマクロ定義が使用されます。)

#define ER(X) errmesg[(X) - ER_ERROR_FIRST]

つまりerrmesg配列から引っ張ってくる、というものです。で、このerrmesg配列はどのように定義されているかというと実はソースコード上には無くて、mysqldの起動プロセス中に呼び出されるinit_errmessage関数がsql/share/errmsg.txtを読み込んで初期化しています。で、さらにこの辺りで国際化が行われてて、、、みたいな話につながります。

要するに、呼び出す側からするとER(エラーコード)でメッセージ文字列に展開されるということです。

ただ、以下のように書式付文字列のままなので、これだけだと不十分です。

Incorrect column name '%s'

そこで、my_snprintfとかで加工しているコード例は無いかどうかを探したところ、いくつかでてきました。

./sql/sql_table.cc:           my_snprintf(warn_buff, sizeof(warn_buff), ER(ER_TOO_LONG_KEY),
./sql/sql_table.cc:       my_snprintf(warn_buff, sizeof(warn_buff), ER(ER_TOO_LONG_KEY),
./sql/sql_table.cc:      length= my_snprintf(buff, sizeof(buff), ER(ER_OPEN_AS_READONLY),

この例に従って書けば良さそうです。

■コード例

MYSQL_ERRMSG_SIZEの値は512です(include/mysql_com.h)。

char warn_buff[MYSQL_ERRMSG_SIZE];
my_snprintf(warn_buff, sizeof(warn_buff), ER(ER_TOO_LONG_KEY), length);
push_warning(thd, MYSQL_ERROR::WARN_LEVEL_WARN, ER_TOO_LONG_KEY, warn_buff);

■補足

最初はpush_warning関数でWARN_LEVEL_ERRORを指定すればエラーとして処理されるのかと思いましたがそうではありませんでした。Errorはまた別の関数を使用する必要があります。

Errorの出力方法

Errorを出力するには、my_error関数を使用します。mysys/my_error.cに実装されています。

int my_error(int nr, myf MyFlags, ...);

こちらはかなり単純です。第1引数nrにはエラーコードを渡します。するとmy_error関数内部でエラーメッセージとの紐付けを行ってくれます。エラーメッセージが書式付文字列である場合には、当てはめたい値を第3引数以降を使用して渡します。

第2引数のMyFlagsはMySQLのユーティリティ関数("my_"というprefixが名前についている関数)ではよく見かける引数でここでは単純に"MYF(0)"を渡せばOKです。

■コード例

my_error(ER_UNKNOWN_COLLATION, MYF(0), buf);