可変引数を持つ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とか呼ばせたい放題というわけですね。
まとめ