MySQL Casual Talks vol.6 で N:1レプリケーション 書き換えについて話してきたこと

去る 2014年07月11日、MySQL Casual Talks vol.6 が開催され、例のごとく LT してきました。
ちとごたごたして書いてなかったので、今更だけど書き綴る。


MySQL Casual Talks vol.6 の自分以外のトークについては @namikawa さんがまとめられているので、そちらを参考に → 「MySQL Casual Talks vol.6」に参加してきた&資料まとめ #mysqlcasual - 元RX-7乗りの適当な日々

マスタN対スレーブ1 レプリケーションの課題

毎度飽きずに、異なるデータセットを持つ複数台のマスタDB から 1台のスレーブDB をつくる手法、通称 "N:1 レプリケーション" のトークをしています。

3年前に N:1 レプリケーションを実装してから今までホントに安定して動き続けていて、今この記事を書いている (そしてあなたが読んでいる) この瞬間にも、2秒に1回レプリケーション対象のマスタサーバを切り替えているのですが、さすがにこれだけの期間使っていると課題もあります。

例えば、MHA による マスタDB のスイッチが発生した場合に追随する手段がないことは前からどうにかしないとなぁと思いつつ、頻繁に発生することでもないし、その時対応すればいいかなぁなんて感じで、はぐらかしていた課題でもあります。

ただ、実を言えば、自分の中で最も大きい課題は、 N:1 レプリケーションを動かすコードにテストがないことでした。

コードの書き換え

そんなわけで、もともと SwitchMaster という名前で公開していたコードに手を入れることに。

まず最初にしたのは、テストを実行するための環境作り。

ほとんど1ファイルにベタに記述されていた実装を、 SwitchMaster package として切り出し、もとのファイルはその package を呼び出すだけにしました。

その後、perl には minila という、CPAN モジュール開発支援ツールがあるので、これをつかって、テストをしやすい環境を構築しました。

これで、元々のコードに対して、テストコードがかけます。

あとは、テストを壊さないようにモジュール分離を進めつつ、テストコードを健全にしていくだけでした。

MHA 対応を図った副作用

ただ、このあたりで MHA 対応できないかなー という悪魔の誘惑が。

現在のところ、(スライドにもあるとおり) MHA によるマスタスイッチに自動的に追随することはできませんが、ちこっと MHA を書き換えれば何とかなるハズ。少なくとも graceful master switch に関しては、master_ip_online_change_script への引数にオリジナルマスタのbinlog 情報と新マスタのbinlog 情報があればどうにかなるのですよ。

ま、とはいえなるべく MHA に手を入れることなくどうにかしたいので、何か別の手段を考えています。出来るのか分からないけど。

んでこのあたりで気づいてしまったことがもう一つ。 MHA によるマスタサーバの入れ替えって、 master switch と呼ばれているのですよ。

元々、自分が書いていたコードは SwitchMaster。 なんだか紛らわしい。

ということで、名前を SwitchMaster から N1Repl に変えました。
(リポジトリ名も変えちゃったけど、fork もされてないようなコードだったしいいよね)

N1Repl

コードは https://github.com/do-aki/N1Repl にありますので、もし良ければ見てやってください。
そして、酷いと感じたコードについては指摘してやってください。

それと、スライドの英語についてもおかしな記述があったなら指摘もらえると助かります。

最後に

主催の @myfinder さん、会場提供の オラクルさん。ありがとうございます。

そろそろホントにネタ切れなのどうにかしないとイケナイ。

闇PHP勉強会でphpのランダマイズについて話してきたこと

導入

"配列のランダマイズ、出来ますか?" を読み、php の場合はどうなの? と思ったので、調べてみましたというお話。

結論としては、4.3.0 以降の php では等分散されます。

ただ、疑似乱数生成器が libc 依存なため OS によって異なる結果となるので、異なる環境での再現性が必要な場合は、mt_rand 使って再実装した方が良いんじゃないかなと。

mt_rand の精度について

メルセンヌツイスタの精度って32bit だけど、64bit 版の場合の mt_rand はどうなの? という質問を受けまして、
合成して 64bit 作ってると言ってしまいましたが、アレは大嘘でした。ごめんなさい。

32bit で生成した乱数を、long にキャストして返してますね。
それどころか、(php_rand の挙動に合わせる? ために)1bit 右シフトして 31bit にしています。

うむむ。。。

乱数の再現性について

ちとここについては。説明不足だったかな。
多くの疑似乱数生成器では、同じ seed を与えると、常に同じ順序で乱数が生成されます。

srand(1) した後に rand() を何度か呼んでみると分かりますが、順に同じ値が返ってくると思います。

ただ、スライドでも書いたとおり、rand は libc 依存なため、異なる環境だと、同じ seed でも異なる乱数が生成されてしまします。

これが mt_rand の場合は実装に依存した動きになるため、異なる環境でも seed が同じならば 、常に同じ順で乱数が生成されるというわけです。

mt_srand(1) をすれば、どの環境でも mt_rand() は順に同じ値(1244335972→15217923→1546885062)が返るということです。 (もちろん、php のバージョンによって実装が変化する可能性はありますが)

ま、これが必要となるケースの場合、 php を選択するなという話になると思いますけど。

多いですね

CentOS6 でデフォルトレイアウトでインストールしてしまったけど後から一つのパーティションにまとめたメモ

概要

ある程度のディスク容量がある場合、なーんも考えずにそのままインストールすると、 /home に多くの容量が割り当てられます。

これはこれで使いやすいケースもあるのだけど、/home を / に統合して一つの空間として使いたかったのでまとめた時のメモ。


追記: /home の中身は残してないので注意

インストール直後
# df -h
Filesystem                   Size  Used Avail Use% Mounted on
/dev/mapper/vg_kvm-lv_root   50G  1.1G   46G   3% /
tmpfs                        3.8G     0  3.8G   0% /dev/shm
/dev/sda1                    485M   55M  405M  12% /boot
/dev/mapper/vg_kvm-lv_home   860G  200M  816G   1% /home          # <- これいらない
統合後
# df -h
Filesystem                   Size  Used Avail Use% Mounted on
/dev/mapper/vg_kvm-lv_root   909G  1.1G  862G   1% /              # <- ここに統合
tmpfs                        3.8G     0  3.8G   0% /dev/shm
/dev/sda1                    485M   55M  405M  12% /boot

/home の削除

アンマウントしてから、 lvremove で論理ボリュームを削除

# umount /dev/mapper/vg_kvm-lv_home

# lvremove /dev/mapper/vg_kvm-lv_home
Do you really want to remove active logical volume lv_home? [y/n]: y
  Logical volume "lv_home" successfully removed

# vgdisplay
  --- Volume group ---
  VG Name               vg_kvm
  System ID
  Format                lvm2
  Metadata Areas        1
  Metadata Sequence No  5
  VG Access             read/write
  VG Status             resizable
  MAX LV                0
  Cur LV                2
  Open LV               2
  Max PV                0
  Cur PV                1
  Act PV                1
  VG Size               931.02 GiB
  PE Size               4.00 MiB
  Total PE              238341
  Alloc PE / Size       14768 / 57.69 GiB
  Free  PE / Size       223573 / 873.33 GiB
  VG UUID               oLnJVY-CoB5-1Hgu-0EcD-PDtf-uEuc-ruUkL0

Free が増えている

root の論理ボリュームを拡張

lvextend の -l オプションに vgdisplay で表示された Free PE を指定して、root の論理ボリュームを拡張する。

# lvextend -l +223573 /dev/mapper/vg_kvm-lv_root
  Extending logical volume lv_root to 923.33 GiB
  Logical volume lv_root successfully resized

# vgdisplay
  --- Volume group ---
  VG Name               vg_kvm
  System ID
  Format                lvm2
  Metadata Areas        1
  Metadata Sequence No  6
  VG Access             read/write
  VG Status             resizable
  MAX LV                0
  Cur LV                2
  Open LV               2
  Max PV                0
  Cur PV                1
  Act PV                1
  VG Size               931.02 GiB
  PE Size               4.00 MiB
  Total PE              238341
  Alloc PE / Size       238341 / 931.02 GiB
  Free  PE / Size       0 / 0
  VG UUID               oLnJVY-CoB5-1Hgu-0EcD-PDtf-uEuc-ruUkL0

Free が 0 になっていることを確認。

ここまではサクサク進む。

ファイルシステムを拡張

論理ボリュームが増えても、ファイルシステムを拡張しなければ利用出来ない。
resize2fs でファイルシステムを拡張する。

これ、結構時間かかる。

# resize2fs /dev/mapper/vg_kvm-lv_root
resize2fs 1.41.12 (17-May-2010)
Filesystem at /dev/mapper/vg_kvm-lv_root is mounted on /; on-line resizing required
old desc_blocks = 4, new_desc_blocks = 58
Performing an on-line resize of /dev/mapper/vg_kvm-lv_root to 242045952 (4k) blocks.
The filesystem on /dev/mapper/vg_kvm-lv_root is now 242045952 blocks long.

これで完了。

LVM の操作、たまにしかやらないからよく忘れる。

第1回 PHP勉強会@相模原で、php 処理系の全体像について話してきました

@yoya さん主催のPHP勉強会@相模原 に参加し、トークしてきました。

当初は SAPI 周りのことを入念に調べて、SAPI が何をするモノで、どのように動くのかを発表するつもりだったのですが、SAPI って実装ごとにかなり性格が違う。
結局、SAPI については処理の流れを解説することのみにとどめ、もっと表面的な部分、php 言語処理系全体の概要を中心に解説しました。

恐らく、php というと <?php で始まる php script が広く認知されているのではないかと思います。
しかし、その php script が動くための動作原理や、$_GET や $_POST のようなあらかじめ用意された変数はどこから生まれてくるのかなどを、きちんと説明できる人は少ないのではないでしょうか。

とはいえ、自分の周りでは説明できる人が比較的多いようなので、今回のトークも釈迦に説法になるのではないかと危惧しましたが、
それでも楽しんでいただけたようで幸いです。

質疑で、なぜphpの処理系のコードを読むようになったのかと聞かれました。
最初のきっかけは xdebug にあったバグの原因を調べ、コントリビュートできたこと ( http://d.hatena.ne.jp/do_aki/20100510/1273510642 ) でした。

このときは、 php 処理系について全く理解せずに、インスピレーションを頼りにソースを追った気がします。

とか思ったら、その前に PDO の処理を読んでたようですね自分。( http://d.hatena.ne.jp/do_aki/20100221/1266746673 )

結局、何がきっかけだったんだろう……?

PHP 文字列リテラルにおける変数展開ノ全テ

はじめに

本記事は、PHP Advent Calendar 2013 18日目です。

前日は @oasynnoum さんの http://oasynnoum.blogspot.jp/2013/12/raspberry-pi-php.html でした。

ラズパイ x php ってあまり見ない組み合わせだったので、なかなかに新鮮でした。


さて、まず初めに大事な前提を。
この記事に書かれている内容は php-5.5.7 のコードをベースにしています。それ以前のバージョンでもだいたい同様に動くと思いますが、今後のバージョンで同じように動作するかどうかは分かりません。


世にも奇妙な php

早速ですが問題です。

code.1 は、何が出力されるでしょうか?

code.1
<?php

echo "${" . " . " . "}";


ぱっと見では "${ . }" が出力されそうに見えるこのコード、正解は「何も出力されない」です。
(環境によっては Notice が発生しますが。)



では次、 code.2 において、関数 echo_yield_key の戻り値は何でしょう?

code.2
<?php

function echo_yield_key(array $_) {
   echo "${_[yield]}";
}

$what_is_this = echo_yield_key(['yield' => '']);


php は、関数に return が無い場合、その戻り値は NULL になります。ですので、普通に考えると $what_is_this は NULL になるように思えます。

が、正解は Generator。

とてもとても不思議な挙動ではありますが、echo_yield_key の呼び出しによって、 Generator が生成されます。



これらの奇妙な挙動ですが、 php の変数展開をきちんと知れば納得……するかどうかは別として、きちんと説明できます。

本記事では、PHP の文字列リテラルにおける変数展開について解説します。

3種の変数展開

マニュアルによれば、文字列における変数の展開方法は大きく分けて2つ。単純な構文と複雑な構文があります。(http://www.php.net/manual/ja/language.types.string.php#language.types.string.parsing)

しかしここでは、複雑な構文については、より細かく分類し、以下の3種類にわけて説明していきます。

  1. 単純な構文
  2. 複雑な構文1 ({$variable} タイプ)
  3. 複雑な構文2 (${expr} タイプ)

単純な構文

単純な といいつつ、実はあまり単純ではありません。単純な構文の変数しか解釈することができないというだけです。
この方式では以下のルールによって変数展開されます。

  1. $ に続く LABEL *1 を変数名として採用します。 *2
  2. [] による 配列(あるいは文字列)の要素指定は可能ですが、添字を引用符で囲うことは''できません''。また、多次元配列も指定できません。 *3
  3. プロパティにアクセス可能ですが、プロパティの配列要素やプロパティのプロパティは指定できません。 *4


code.3 に例を挙げました。

code.3
<?php

/*** 1 ***/
$hoge = 'HOGE';
$fuga = 'FUGA';
echo "$hoge is not $fuga!" . PHP_EOL;  // HOGE is not FUGA!


/*** 2 ***/
$a = [
  0      => 'ZERO',
  'hoge' => 'HOGE',
  99     => ['ARRAY'],
];

echo "$a[0]" . PHP_EOL;       // ZERO
echo "$a[hoge]" . PHP_EOL;    // HOGE (ここでは、ベアワードを使うことに対する Notice は発生しません)
echo "$a['hoge']" . PHP_EOL;  // syntax error!
echo "$a[99][0]" . PHP_EOL;   // Array[0]  ($a[99] が評価されて Notice:  Array to string conversion が発生。その後に続く [0] は素の文字列としてそのまま出力されます)


/*** 3 ***/
$b = new stdClass();
$b->prop = 'property';
$b->self = &$b;

echo "$b->prop" . PHP_EOL;       // property
echo "$b->prop[0]" . PHP_EOL;    // property[0] ($b->prop までが評価されて、[0] は素の文字列としてそのまま出力されます)
echo "$b->self->prop" . PHP_EOL; // (Catchable fatal error:  Object of class stdClass could not be converted to string が発生)

複雑な構文1 ({$variable} タイプ)

基本的に、 通常の PHP 構文の中で $ から始まる変数参照はすべて使えます。
また、単純な構文と異なり、配列の要素指定では引用符が利用でき、ベアワードを使うと通常通り Notice が発生します (code.4)

code.4
<?php

$a = array(
  0      => 'ZERO',
  'hoge' => 'HOGE',
  99     => ['ARRAY'],
);

echo "{$a[0]}" . PHP_EOL;       // zero
echo "{$a[hoge]}" . PHP_EOL;    // HOGE (Notice:  Use of undefined constant hoge - assumed 'hoge')
echo "{$a['hoge']}" . PHP_EOL;  // HOGE
echo "{$a[99][0]}" . PHP_EOL;   // ARRAY


もちろん、プロパティも使えますし、プロパティのプロパティも参照可能です。
さらにメソッド呼び出しも可能であるため、メソッドチェインもOKですし、 Array デリファレンスも有効です (code.5)

code.5
<?php

class X {
  public $prop = 'PROPERTY';
  public $self = null;

  public function self() {
    return $this;
  }
  public function get() {
  	return __CLASS__;
  }
  public function getArray() {
  	return array(__CLASS__);
  }
}

$x = new X();
$x->self = $x;

echo "{$x->prop}" . PHP_EOL;        // PROPERTY
echo "{$x->self->prop}" . PHP_EOL;  // PROPERTY

echo "{$x->self()->self()->get()}" . PHP_EOL;          // X
echo "{$x->self()->self()->getArray()[0]}" . PHP_EOL;  // X


変数の参照はできますが、その変数を使った演算はできません (code.6)

code.6
<?php

$one = 1;

echo "{$one + 2}" . PHP_EOL; // PHP Parse error:  syntax error, unexpected '+', expecting '}'


Closure の呼び出しも $ で始まるので、当然呼び出し可能です。
呼び出しの中だけは、演算可能で、関数も呼べます。 (code.7)

code.7
<?php

$f = function($a) { return "received values is " . $a; };
$one = 1;

echo "{$f($one + 1)}" . PHP_EOL;   // received values is 2
echo "{$f(abs(-1))}" . PHP_EOL;    // received values is 1

複雑な構文2 (${expr} タイプ)

通常の変数参照については、単純な構文よりも貧弱です。
変数そのものか、あるいは配列の要素しか参照できません。プロパティでさえ。 (code.8)

code.8
<?php

$hoge = 'HOGE';
echo "${hoge}" . PHP_EOL; // HOGE

$ary = ["ZERO"];
echo "${ary[0]}" . PHP_EOL; // ZERO

$obj = new stdClass();
$obj->prop = "PROPERTY";
echo "${obj->prop}" . PHP_EOL; // PHP Parse error:  syntax error, unexpected -> (T_OBJECT_OPERATOR)


しかし、この構文の強力かつ変態的なところは、上記以外のケースです。
"${" から "}" あるいは "[" までの間の文字が変数名として有効でない場合*5は、"${" から "}" の間が PHP の式として評価され、その評価値が示す変数の値が参照されます。 (code.9)

code.9
<?php

$alpha = 'abcdefghijklmn...';
$abc = "abc's value";
echo "${substr($alpha, 0, 3)}" . PHP_EOL; // substr($alpha, 0, 3) が評価されて、 "abc" となり、 $abc が参照される

$dim0 = 'hoge';
$dim1 = 'fuga';
$dim2 = 'bar';
for ($i=0; $i < 3; ++$i) {
    // "dim$i" が評価されて、 "dim0"、"dim1"、"dim2" となり、それぞれの変数が参照される
    echo "${"dim$i"}" . PHP_EOL; // hoge, fuga,bar
}


ダブルクォテーションの中にダブルクォテーションが入っても、"${" と "}" の間なので、文字列終了とは見なされないあたりがとても深淵ですね!

冒頭のコードを解説

冒頭のコードですが、奇妙な挙動は全て、複雑な構文 ${expr} タイプ の文字列によって引き起こされています。

code.1 の 「"${" . " . " . "}"」 は、${ の後がダブルクォテーションで、これは変数名ではないので式として評価され、 「" . " . " . "」 つまり、 「" . . "」 (space dot space space dot space) という名前の変数の値が出力されることになります。

通常、この変数には何も代入されていないため NULL と評価(このとき未代入変数の使用による Notice が発生します) され、空の文字列が生成されます。

余談ですが、" . . " という名前の変数は通常の変数表現で表すことはできませんが、 code.10 の (A) のように代入することができ、 (B) のように参照することができます。

続く echo がどれも同じ変数を参照することは、もう分かりますね。

code.10
<?php

${" .  . "} = 'hoge';  // (A)
echo  ${" .  . "};     // (B)

echo "${" .  . "}";      // hoge
echo "${" . " . " . "}"; // hoge


この形式での変数代入(及び参照)の仕方を使うと、バイナリも変数名として使えるようになります。
2日目の記事 PHP の配列を使った手品とその種明かし と同じ事が、配列のキーではなく変数名でも可能だったりします。 (code.11)

code.11
<?php

${hex2bin('00')} = 'value_0';
${hex2bin('01')} = 'value_1';
${hex2bin('02')} = 'value_2';
var_dump($GLOBALS);
  /*
  array(13) {
    (中略)
    '\0' =>
    string(7) "value_0"
    '' =>                  //  表示されてないけど 0x01
    string(7) "value_1"
    '' =>                  //  表示されてないけど 0x02
    string(7) "value_2"
    'GLOBALS' =>
    &array
  }
  */

閑話休題

code.2 ですが、まず "${" から "[" の間の文字 "_" が変数名として解釈されます。
そして、その要素を参照することになる訳ですが、この "[" と "]" に囲われた文字についても、 php の式として評価されます。
ただし、変数として有効な文字列だと変数参照される "${" ... "}" と異なり、こちらには無条件で php の式を入れることができます。
つまりどういう事かというと、一部のキーワード、例えば yield が記述可能なのです(code.12)

これにより、yield が評価され、Generator が生成されます。

code.12
<?php
echo "${yield}" .PHP_EOL;     // "yield" が変数名として有効なので、 $yield が参照される
echo "${_[yield]}" .PHP_EOL;  // yield 式として評価される

そして、その''一部のキーワード'''には、 exit や die も含まれます。
code.13 では、台詞を聞くことなく終了します。

code.13
<?php

"${_[die]}";

echo <<<EOM
あ…ありのまま 今 起こった事を話すぜ!
「俺は文字列を書いていたと思ったらいつの間にか終了していた」
な… 何を言っているのか わからねーと思うが 
おれも 何をされたのか わからなかった…
EOM;


恐ろしや!


まとめ

歴史的な事情もあるのでしょうが、php の文字列における変数展開はとてつもない闇を秘めています。

この闇を使いこなせるかどうかは、php を利用しているあなた次第です。



自分としては、変数展開するなら、複雑な構文1 ({$variable} タイプ) をオススメしたいところ。

単純な構文 を使った場合、変数を表す文字列とそれ以外の文字列の区別がわかりにくくなるケースがあるし、複雑な構文2 (${expr} タイプ) は、黒魔術的な側面が強いです。

とはいえ、実は、複雑な構文1 ({$variable} タイプ) でも、メソッドやクロージャ呼び出しの引数については、php の式を記述可能だったりします。
これはつまり、 code.14 で突然死するということです。

code.14
<?php
$f = function(){};
"{$f(exit)}";      // 突然の死

echo "php まじカオス";

変数展開は、用法用量を守って正しく使いましょう。



明日は @ne_sachirou さんです。

*1:LABEL [a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]* / Zend/zend_language_scanner.l@1013

*2: Zend/zend_language_scanner.l@1889

*3: Zend/zend_language_scanner.l@1881

*4: Zend/zend_language_scanner.l@1871

*5: Zend/zend_language_scanner.l@1490

UNSIGNED の不思議な挙動 #mysqlcasual

かじゅある!

この記事は、MySQL Casual Advent Calendar 2013 4日目です。

3日目を数秒差で @kamipo さんに取られてしまい*1、ネタかぶったらどうしようとドキドキしていましたが、そのようなことはなくこれでようやく安眠できそうです。

MySQL では、UNSIGNED が使える

UNSIGNED は、標準SQL にはないデータ型属性です。*2

通常の INT が -2147483648 から 2147483647 の範囲を表すのに対し、
INT UNSIGNED で定義したカラムは 0 から 4294967295 の範囲となります。


今回は、この UNSIGNED に関する奇妙な挙動のお話。


なお、検証に利用したバージョンは 5.1.69 です。
すでに 5.6 が GA となっている現在からするとやや古いバージョンではありますが、REHL 6.5 で採用されているバージョンでもあるので、これを利用している方はまだ多いのではないでしょうか。

突然の 4294967295

こんなテーブルがあるとしましょう。

CREATE TABLE test(
  id VARCHAR(10) PRIMARY KEY,
  i INT,
  ui INT UNSIGNED
);
INSERT INTO test VALUES
  ('max',2147483647, 4294967295),
  ('min',-2147483648, 0);

SELECT * FROM test;
+-----+-------------+------------+
| id  | i           | ui         |
+-----+-------------+------------+
| max |  2147483647 | 4294967295 |
| min | -2147483648 |          0 |
+-----+-------------+------------+

id は分かりやすさのために付けただけなので、注目すべきは i (INT) と ui (UNSIGNED INT)

それぞれ max には最大値、min には 最小値 が格納されています。

ここで、範囲を超えるような演算をしてみましょう。

UPDATE test SET i=i+1, ui=ui+1 WHERE id='max';
UPDATE test SET i=i-1, ui=ui-1 WHERE id='min';

Warning はでますが、エラーとはならずに実行出来ます。

そして結果がこれ。

SELECT * FROM test;
+-----+-------------+------------+
| id  | i           | ui         |
+-----+-------------+------------+
| max |  2147483647 | 4294967295 |
| min | -2147483648 | 4294967295 |
+-----+-------------+------------+

( ゚д゚)……(つд⊂)ゴシゴシ…… えっ?



なぜか INT UNSIGNED だけ、最小値から減算すると 4294967295 になる。


上記例では 1 を引いてますが、いくつを引いても 4294967295 でした。

UPDATE test SET ui=0 WHERE id='min';          -- 一度 0 に戻して
UPDATE test SET ui=ui-5000 WHERE id='min';    -- 大きい数字を引いても
SELECT * FROM test;
+-----+-------------+------------+
| id  | i           | ui         |
+-----+-------------+------------+
| max |  2147483647 | 4294967295 |
| min | -2147483648 | 4294967295 |
+-----+-------------+------------+

_人人人人人人人人人人_
> 突然の 4294967295 <
 ̄Y^Y^Y^Y^Y^Y^Y^Y^Y ̄


わけが分からない。


数値を文字列で渡してみる

先ほどのテーブルを一度初期値(最大値/最小値)に戻します。

UPDATE test SET i=2147483647, ui=4294967295 WHERE id='max';
UPDATE test SET i=-2147483648, ui=0 WHERE id='min';
SELECT * FROM test;
+-----+-------------+------------+
| id  | i           | ui         |
+-----+-------------+------------+
| max |  2147483647 | 4294967295 |
| min | -2147483648 |          0 |
+-----+-------------+------------+

今度は、数値をシングルクォテーションで囲って、文字列で渡してみましょう。

UPDATE test SET i=i+'1', ui=ui+'1' WHERE id='max';
UPDATE test SET i=i-'1', ui=ui-'1' WHERE id='min';


前回同様 Warning はでるものの、実行はできます。

シングルクォテーションで囲ったところで数値として解釈されるはず。
なので挙動は変わらないはず……と思って結果を見ると

SELECT * FROM test;
+-----+-------------+------------+
| id  | i           | ui         |
+-----+-------------+------------+
| max |  2147483647 | 4294967295 |
| min | -2147483648 |          0 |
+-----+-------------+------------+

( ゚д゚)……(つд⊂)ゴシゴシ…… えっ?



なぜかこちらは、保存されたデータが変化することはありません。
や、むしろこの方が適切な挙動のように思えます。

けど、なぜ 数値を文字列にするだけで挙動変わる???

わけが分からない。


5.7 で試したら

5,7 で試してみたところ、データが書き換わらない動きに統一されているようです。
「突然の 4294967295」となるクエリについては エラーとなりました。(SET sql_mode='' にて)

エラー文言を見る限り、格納される前に演算の部分でエラーとなっているように見えます。

mysql> SELECT version();
+---------------+
| version()     |
+---------------+
| 5.7.2-m12-log |
+---------------+
1 row in set (0.00 sec)

mysql> SET sql_mode='';
Query OK, 0 rows affected (0.00 sec)

mysql> UPDATE test SET i=i-1, ui=ui-1 WHERE id='min';
ERROR 1690 (22003): BIGINT UNSIGNED value is out of range in '(`test`.`test`.`ui` - 1)'


とはいえ、5.6 以降はsql_mode にデフォルトで STRICT_TRANS_TABLES が含まれている*3ので、あまり問題にはならないかと思います。
範囲外の値が格納されるケースは全て エラーとなって SQL の実行に失敗しますので。

mysql> SET sql_mode='STRICT_ALL_TABLES';
Query OK, 0 rows affected (0.00 sec)

mysql> UPDATE test SET i=i+1, ui=ui+1 WHERE id='max';
ERROR 1264 (22003): Out of range value for column 'i' at row 1
mysql> UPDATE test SET i=i-1, ui=ui-1 WHERE id='min';
ERROR 1264 (22003): Out of range value for column 'i' at row 1

まとめ

5.1でも STRICT_ALL_TABLES を設定したほうが良いんじゃないかな。

とはいえ、既に動いてるシステムで sql_mode 変えるのはしんどいので、おかしなクエリは発行しないよう注意しましょー。

明日は @yoshi_ken さんです。




かじゅある!

AngularJS 勉強会 に参加してきた

前から気になっていて、ささたつさんの記事 を読んでから、これは良いなと思っていつつも、ずっと触れずにいた AngularJS の勉強会が開催されるという情報を得たので、参加してみた。

ほんとは、参加するまでに何か軽く作っておきたかったのだけど、
結局今日の今日まで全く書かずに参加することになってしまった。

とはいえ、まだ触ったことない人向けの話もあったので助かった。

所感

勉強会の時間中に、話を聞きつつ実際に書いてみたのだけど、業務系のアプリをリッチにする用途に向いてそうという印象を受けた。

凝ったことはまだ全然やっていないので、どの程度まで踏み込めるか分からないけど、堅めのUIを動的にする分にはホントに作りやすそうと感じた。

書いてたモノとか

https://gist.github.com/do-aki/7771047

話を聞きつつ、公式の sample 見つつで書いたのがこれ。
一つの textbox に入力したものをカンマや空白区切りのタグと見立てて操作する感じのもの。

双方向バインディングを、ほとんど意識することなくかけるのは便利だと持った。
ただ、表示の加工 (出力方向の制御) や、入力値バリデーション(入力時の制御) についてはまだまだぜんぜん分かってないのでこのあたりは調べる必要がありそう。

さらに、モデル内部でのデータ操作による通知をどのように view に伝えるのかが分からず、ここでは直接 input メソッドを呼んでしまった (これが正しいかどうかは分からない)

複雑なモデル関係を作らず、薄いモデルで作るべし

という話を聞いたので、そういう方向でモデルを構築するのが良いのだろうとは思う。けど、実際にはコレクションの内部に与えた変化を親コレクションに伝播させたいこととかよくあるので、そのへんを補助するものはあってもいいのではーとは思った。

ま、まだちゃんとしたものを作った訳じゃないからわからんけどな。

総括

MVVM 的なアプローチはテスタビリティ高いし、今現在、自分の中では理想的と思えるやりかたと思っているので、これが F/W として普及するのは素敵。

これから積極的に触っていきたい。