可変引数を持つNative関数の実装メモ

可変引数を持つNative関数は実装方法が一般的なNative関数とは異なるためメモしときます。一般的なやつの実装方法はリファレンスマニュアル参照。

概要

編集対象のファイルとその内容についての表。

ファイル 編集内容
sql/item_func.hまたはその派生 新しい関数のためのItem_func継承クラスを定義する。Item_str_funcクラスとかいろいろあるので適宜利用する。
sql/item_func.ccまたはその派生 新しい関数のためのItem_func継承クラスの関数を実装する。
sql/sql_yacc.yy 新しい関数用の内部トークン(シンボル)を定義、またパーサの処理を追加する。新しい関数名がSQL文中に含まれる場合、item_func.h等で追加したItem_func継承クラスのインスタンスを作成してパースツリーに追加する。
sql/lex.h 新しい関数名をSQL関数リストに登録。これにより関数名がコマンドのひとつとして扱われるようになり、パーサ処理でいきなり弾かれることが無くなる。

以下、元から入っているconcat関数を例にコードを抜粋。

sql/item_strfunc.h

concat関数用のクラス、Item_func_concatを定義。Item_str_funcを継承。

class Item_func_concat :public Item_str_func
{
  String tmp_value;
public:
  Item_func_concat(List<Item> &list) :Item_str_func(list) {}
  Item_func_concat(Item *a,Item *b) :Item_str_func(a,b) {}
  String *val_str(String *);
  void fix_length_and_dec();
  const char *func_name() const { return "concat"; }
};

sql/item_strfunc.cc

Item_func_concat::val_str関数とItem_func_concat::fix_length_and_dec関数を実装。val_str関数の中で文字列連結処理とかをやってる。

/*
  Concatenate args with the following premises:
  If only one arg (which is ok), return value of arg
  Don't reallocate val_str() if not absolute necessary.
*/

String *Item_func_concat::val_str(String *str)
{
  DBUG_ASSERT(fixed == 1);
  String *res,*res2,*use_as_buff;
  uint i;
  bool is_const= 0;
...

sql/sql_yacc.yy

この' expr_list 'ってのが可変引数をとるために必要。

%token  CONCAT
...
        | CONCAT '(' expr_list ')'
          { $$= new Item_func_concat(* $3); }

expr_listはこんな感じ。expr_list2が再帰処理を行い、Listに追加してる。

expr_list:
        { Select->expr_list.push_front(new List<Item>); }
        expr_list2
        { $$= Select->expr_list.pop(); };

expr_list2:
        expr { Select->expr_list.head()->push_back($1); }
        | expr_list2 ',' expr { Select->expr_list.head()->push_back($3); };

sql/lex.h

SQLコマンド用文字列"CONCAT"(大文字小文字区別無し)をsql_yacc.yyで指定しておいたトークン名CONCATにマッピング

  { "CONCAT",           SYM(CONCAT)},

動きを観察

こんなテーブルを作ってみる。

[test] > desc t1;
+-------+----------+------+-----+---------+-------+
| Field | Type     | Null | Key | Default | Extra |
+-------+----------+------+-----+---------+-------+
| c1    | int(11)  | YES  |     | NULL    |       | 
| c2    | int(11)  | YES  |     | NULL    |       | 
| c3    | char(10) | YES  |     | NULL    |       | 
| c4    | text     | YES  |     | NULL    |       | 
+-------+----------+------+-----+---------+-------+
4 rows in set (0.00 sec)

[test] > select * from t1;
+------+------+------+------+
| c1   | c2   | c3   | c4   |
+------+------+------+------+
|    1 |  100 | AAA  | aaa  | 
|    2 |  200 | BBB  | bbb  | 
|    3 |  300 | CCC  | ccc  | 
+------+------+------+------+
3 rows in set (0.00 sec)

Item_func_concat::val_str関数にbreakpointを突っ込んで、以下を実行してみる。

[test] > select concat(c1,c2), concat(c3,c4) from t1;
+---------------+---------------+
| concat(c1,c2) | concat(c3,c4) |
+---------------+---------------+
| 1100          | AAAaaa        | 
| 2200          | BBBbbb        | 
| 3300          | CCCccc        | 
+---------------+---------------+
3 rows in set (4.49 sec)

だいたいこんな感じで6回呼ばれた。

#0  Item_func_concat::val_str (this=0xa44a520, str=0xb1764180) at item_strfunc.cc:313
#1  0x0814bffa in Item::send (this=0xa44a520, protocol=0xa411990, buffer=0xb1764180) at item.cc:4821
#2  0x081c1594 in select_send::send_data (this=0xa44aad8, items=@0xa412694) at sql_class.cc:1015
#3  0x08234220 in end_send (join=0xa44aae8, join_tab=0xa44bde0, end_of_records=false) at sql_select.cc:11481
#4  0x08232923 in evaluate_join_record (join=0xa44aae8, join_tab=0xa44bc60, error=0, report_error=0xa411968 "") at sql_select.cc:10725
#5  0x082326c7 in sub_select (join=0xa44aae8, join_tab=0xa44bc60, end_of_records=false) at sql_select.cc:10606
#6  0x0823220c in do_select (join=0xa44aae8, fields=0xa412694, table=0x0, procedure=0x0) at sql_select.cc:10345
#7  0x08221931 in JOIN::exec (this=0xa44aae8) at sql_select.cc:2076

ここで#2のselect_send::send_data関数がwhile文で回していたのでいろいろなSQL文を投げつつ観察。

  protocol->prepare_for_resend();
  Item *item;
  while ((item=li++))
  {
    if (item->send(protocol, &buffer))
    {
      protocol->free();                         // Free used buffer                                                                                                         
      my_message(ER_OUT_OF_RESOURCES, ER(ER_OUT_OF_RESOURCES), MYF(0));
      break;
    }
  }
  thd->sent_row_count++;

上記コードは結果レコード件数分だけ繰り返し実行される。さらにwhileは各結果レコードに含まれるカラム数分(関連している場合のみ)だけ繰り返し実行される。

で、#1はItem::sendとなっているけれども、インスタンスとしてはItem_func_concatかな。

class Item {
...
  virtual bool send(Protocol *protocol, String *str);

となっているのでsendは仮想関数だけれどもオーバーライドしていないだけ。

そしてItem::send関数では、以下のようにfield_type()の戻り値を元に、次に呼び出すべき関数を選定してる。

  switch ((f_type=field_type())) {
  default:
  case MYSQL_TYPE_NULL:
  case MYSQL_TYPE_DECIMAL:
  case MYSQL_TYPE_ENUM:
  case MYSQL_TYPE_SET:
  case MYSQL_TYPE_TINY_BLOB:
  case MYSQL_TYPE_MEDIUM_BLOB:
  case MYSQL_TYPE_LONG_BLOB:
  case MYSQL_TYPE_BLOB:
  case MYSQL_TYPE_GEOMETRY:
  case MYSQL_TYPE_STRING:
  case MYSQL_TYPE_VAR_STRING:
  case MYSQL_TYPE_VARCHAR:
  case MYSQL_TYPE_BIT:
  case MYSQL_TYPE_NEWDECIMAL:
  {
    String *res;
    if ((res=val_str(buffer)))
      result= protocol->store(res->ptr(),res->length(),res->charset());
    break;
  }
...

で、このfield_type()はさらにresult_type()を読んで判定してて、

enum_field_types Item::field_type() const
{
  switch (result_type()) {
  case STRING_RESULT:  return MYSQL_TYPE_VARCHAR;
  case INT_RESULT:     return FIELD_TYPE_LONGLONG;
  case DECIMAL_RESULT: return FIELD_TYPE_NEWDECIMAL;
  case REAL_RESULT:    return FIELD_TYPE_DOUBLE;
  case ROW_RESULT:
  default:
    DBUG_ASSERT(0);
    return MYSQL_TYPE_VARCHAR;
  }
}

Item_func_concatの基底クラスであるItem_str_funcでSTRING_RESULTを返しているので、Item::send関数でのswitch-case文はval_str関数の呼出に至ることになる。

class Item_str_func :public Item_func
{
...
  enum Item_result result_type () const { return STRING_RESULT; }
...

というわけでダラダラ書いてきたけど、適切なクラスを継承するか、あるいは独自にresult_type()を実装するかすればval_strとかval_intとか呼ばせたい放題というわけですね。

まとめ

  • val_str関数等は各結果行につき、SQL関数が使用されている回数分だけ呼び出される。
  • SQL関数の引数データはval_str関数等に1度に渡されてるのであとは宜しくやること。