2012年9月21日金曜日

PHPのarrayの入れ子を作るのに苦労して=&とunsetを使って何とかした話

PHPで、

こんな感じのDBから読んできたデータから、


こんな構造を作りたいなと思ったのです。

こんな感じかな。
<?php
//ここから
function row($type,$typeName,$value1,$value2){
    return array("type" => $type,
                 "typeName" => $typeName,
                 "value1" => $value1,
                 "value2" => $value2);
}
$rows = array(row(1,"あ","ABC","DEF"),
            row(1,"あ","AAA","BBB"),
            row(1,"あ","XXX","YYY"),
            row(2,"い","BEEF","PORK"),
            row(2,"い","COFFEE","TEA"),
            row(2,"い","MILK","COCOA"));
//ここまではサンプルデータを作っているところなので本題とは関係なし。
//データはソートはされているものとする。

$tree = array();    // $treeが最終的なルート配列オブジェクト
$prevType = null;   // Typeが変わったらこの処理をする…というためのもの
foreach($rows as $r){  //1行ずつ読む
    if($prevType !== $r["type"]){ //もしTypeが変わったら
        // 変更チェック用の変数を更新し
     $prevType = $r["type"];
        // 親のノードを新規作成し、$typenodeを差し替え
        $typenode = array("type" => $r["type"],
                          "typeName" => $r["typeName"],
                          "values" => array()); // 親のノードは子を格納する配列を持つ
        // 作った親のノードをルート配列に追加
        $tree[] = $typenode;
    }
    // 子のノードを作成
    $valuenode = array("value1" => $r["value1"],
                        "value2" => $r["value2"]);
    // 今の時点で $typenode で親ノードが参照出来るはずで、
    // それに対して"values"で引ける子ノード格納用配列に、
    // 子のノードを追加
    $typenode["values"][] = $valuenode;
}
実行してみた結果。
( ಠ益ಠ)
ステップ実行してみると、
        :
        // 作った親のノードをルート配列に追加
        $tree[] = $typenode;
    }
        :
    // 今の時点で $typenode で親ノードが参照出来るはずで、
    // それに対して"values"で引ける子ノード格納用配列に、
    // 子のノードを追加
    $typenode["values"][] = $valuenode;
}
この下の方の$valuenodeを追加しているところで、
「$typenode」には追加されるものの、
「$treeからたどれる『$typenodeと同じものであるはずのもの』」
には追加されているように見えない。
        $tree[] = $typenode;
で、$typenodeそのものがコピーされてしまっているように見える。
本当は違っててその後変更が発生しそうになった時にコピーされるんだろうけど。

なのでこうしてみた。=&で参照渡し的な何かになるはずと聞いて。
        $tree[] =& $typenode;
実行してみた結果。
(╬ಠ益ಠ)

今度は、二週目の
        // 親のノードを新規作成し、$typenodeを差し替え
        $typenode = array("type" => $r["type"],
                          "typeName" => $r["typeName"],
                          "values" => array()); // 親のノードは子を格納する配列を持つ
        // 作った親のノードをルート配列に追加
        $tree[] =& $typenode;
の上の方のところで「新しいノードを作って$typenodeを差し替えた」だけのつもりが、
既に一周目でセットした$tree[0]の要素までも差し替わってしまった。

参照渡し…ってそこまで参照なの!?
これ、そもそもマインドモデルが上の図と違ってて、模式図で表せない。

最終的にこうしたら何とかなった。
$tree = array();
$prevType = null;
foreach($rows as $r){
    if($prevType !== $r["type"]){
        $prevType = $r["type"];
        unset($typenode);
        $typenode = array("type" => $r["type"],
                          "typeName" => $r["typeName"],
                          "values" => array());
        $tree[] =& $typenode;
    }
    $valuenode = array("value1" => $r["value1"],
                        "value2" => $r["value2"]);
    $typenode["values"][] = $valuenode;
}
差し替える前にunsetをする事で、
$typenodeというシンボルと実体との紐付けが解放されるので、
あらためて作ったarrayをセットしても既存のデータ構造に影響を与えない。

なんだこの仕様。

array関数で新たに配列作って変数に代入する、って
「mallocしてそのポインタを返す」的な感覚かと思ってたら、
「返す先のポインタがあったら指している領域に上書きする」みたいな
日本語すら怪しくなるような動きもするとは。
理解に苦しむ。

2012年9月18日火曜日

mroongaのデータを壊してテーブルも消せなくなったのを何とかした風

先週末ハマった件のメモ書き。
これで合っているかどうか保証も何も全く無いので
参考にする場合は自己責任で。

mroongaをストレージモードで使った開発中、
安直な設計でテーブル作って膨大なデータのinsertクエリを実行してしまった。

全然戻ってこないのでクエリのキャンセルをしたのだがそれでも戻ってこず。
mysqldのrestartも実行したが戻ってこず。
仕方なくkill -9で抹殺。

その後再起動し、この設計じゃダメだなと思いdrop tableしようとしたら
drop tableも戻ってこない。

プロセスリストを見てみると
select count(*) into @discard from `information_schema`.`PARTITIONS`
というSQLをdebian-sys-maintが実行していて全然戻ってきていない。
こいつがロックしている線が濃厚。


当該のSQLやdebian-sys-maintでググってみると
「debian系のおせっかいだからスクリプトいじって黙らせちゃえよ」
みたいな記事が見つかるが、
それもなんだかイカン気がする。

やけくそでmysql止めて
/var/lib/mysql/【スキーマ名】.mrn.*
のファイルを全消しして
再起動してdrop tableしてみる。
瞬時にdisconnected from server的なメッセージが出て、
mysqldのプロセスIDが変わってる。
悪化/(^o^)\

repair tableとかあがいてみる。
「このエンジンはリペアとかないよ」的なメッセージが出る。

そこでふと、以前mysqlのバージョンだけ上がってmroongaプラグインが読み込めなくなって
mysqlの起動が出来なくなってヒイコラした時、
その後drop tableは普通にできたよなーという記憶が。

ということでUNINSTALL PLUGINして、dropしてみる。
…成功!


あらためてINSTALL PLUGINして、テーブル作ってまともな量insert。
無事動作している模様。


多分こうなった場合、
  1. UNINSTALL PLUGIN
  2. drop table
  3. mrnファイル削除
  4. INSTALL PLUGIN
  5. create table
とすればいいのかなという気がしているけど
実はまずいのかもしれない。不明。


やってみる場合自己責任で。