がるの健忘録 このページをアンテナに追加 RSSフィード

2017-07-22

[][][]多言語対応のあれこれ

ふと生徒さんに質問をいただいたのもあって。

ちょうどよいきっかけになったので、せっかくなんでBlogで。


本質的には「どの言語のどの領域」でもある程度応用が利くかと思われますが。

一応、おいちゃんの記述なんで「MySQLPHPつかったWebアプリケーション」をど真ん中に据えて、ってな感じで。


まず「ユーザからの入力について多言語対応したい」は簡単で「保存するデータ及びHTMLUTF-8にしましょう」で、fin。

いやまぁUnicodeであれば大体無問題だと思われるのですが*1HTMLとかでUTF-16って、あんまり見た記憶がないんですよねぇその辺詳しい諸氏の突っ込み求む。

ひとつポイントがあるとすると「MySQL文字コードは、utf8じゃなくてutf8mb4」ってあたりなのですが、そのためにはMySQL5.5.3+というバージョンが必要なのでそれより低いバージョンの場合はバージョンをあげましょうここに慈悲はない。


お次に「プログラム的に動的な文字の出力」をどうするか、ですが。例えば「お知らせ」とか。

この辺あたりから少し面倒になるのですがおおむね

・ユーザが「表示してほしい」言語を忖度する

忖度した結果としての「選択された言語」のデータを引っ張ってくる

ってまぁ、こんな感じ。


まぁ最終的には「ユーザに選んでもらう」でよいですし、選んだ結果は「Cookieあたりに保存」しておけばよいのですが。

初手のアクセスで「きっとこの人は日本語圏の人なのではなかろうか?」を推測したいのであれば、HTTP RequestヘッダのAcecpt-Languageを見ると、比較的、ヒントがあったり。

PHPの場合

$_SERVER['HTTP_ACCEPT_LANGUAGE']

で取得可能。帰ってくる値は、例えば

"ja,en-US;q=0.7,en;q=0.3"

ってな感じなので。初手にjaがあったら「なんとなくこの人、日本語圏の人なのではないだろうか?」と推測が可能。

それ以外は適宜しらべて。en-USとかきたら英語圏だし、それ以外で斜めに調べた限りだと「de (ドイツ語)」「es (スペイン語)」「it (イタリア語)」など。

後ろの「q=0.7,en;q=0.3」にも本来的には意味があるので、興味がある諸氏は適宜しらべたし(大まかには、各言語の優先確率)。

雑に行くんなら「先頭2文字で判断」でも、当面は困らないんじゃないかなぁ切り出しておいて問題が起きたら修正すればいいんだし(雑)。


で、あとは例えば「お知らせ」なら、お知らせテーブルに「言語」とかいうカラムをつけておいて

ja: お知らせです。

en: It is news

it:saluti

とかって感じでデータを用意して出力すれば、それでOKな感じ。どっちかってぇと「各言語のコンテンツ」用意するのが面倒だよねぇ、的な。

でもまぁそれはコンテンツ用意する人の問題なので、サイトの骨格であるシステム作成のおいちゃん的にはいったん気にしないw


さて割と一番大きな本題「HTMLなどの静的*2なファイルの文字」をどうするか、ですが。

大枠として2種類あって、かつその2種類にはそれぞれ亜種がいくつか存在します、ので、それぞれ、ある程度(もしくは簡単に触りだけ)説明をしていきたいかなぁ、ってのが、本文章の趣旨。


大まかには

テンプレートを切り替える

・出力文字を切り替える

の2種類。

それぞれ、少しかみ砕いて。


テンプレートを切り替える

いやまぁそのまんまなのですが。

例えば「ログイン」Pageがあるとして、ボタンに「ログイン」とか日本語で書かれると、英語圏の人は多分いろいろと困るです。

……いや日本のサイトで「login」って書いてあっても困らない気がビシバシとするのですが、その辺は置いといて。


てっとり早いのは「英語圏用のテンプレート」と「日本語圏用のテンプレート」とを別々に用意して出力を切り替える、って方法がありまして。

対応言語数が少ない&ページ数が少ない&更新頻度が低い(更新そのものが少ない)のであれば、割と手っ取り早い解決策だと思われます。


方法としては

・言語ごとにディレクトリを分ける

・言語ごとに拡張子を分ける

って方法がありまして。


例えば(Smartyチックに)login.tpl、ってテンプレートがあるとしますと。

ディレクトリで分ける」パターンであれば、

templates/ja/login.tpl

templates/en/login.tpl

って風に入れて。

拡張子で分けるのであれば、(Smarty的に「本当の意味での拡張子」はいじると面倒なんで)

templates/login.ja.tpl

templates/login.en.tpl

って感じにすると、切り分けられます。


上述のような切り替えの作業は、どこか一か所にまとめておくといろいろと楽ですよね。

うちのフレームワーク(MagicWeapon)であれば、viewクラスのmake_template_filename()メソッドを上書きして、って感じかなぁ。

まぁ大体の*3コードであれば、どこかしら「テンプレートのファイル名を取得する」的な一点があると思うので、そこを「キュッ」と絞めると、いけると思います。


出力文字を切り替える

対応言語数が多い&ページ数が多い&更新頻度が高いのであれば、「出力文字による切り替え」を想定したほうが楽かもしれません……初手面倒ですが。

端的には

・各言語用の翻訳ファイルを用意して

プログラムを通して文字列を変換する

となります。


これにも方法がいくつかあって、大まかには

サーバサイドで自力実装

クライアントサイドで自力実装

・「gettext」ってのが割とあちこちの言語でライブラリとして存在するので、それを使う

のいずれか、になります。


共通があるのでgettextがよさそうなもんですが…それなりに使い方とかお作法とか癖とかがあるので。

PHPの場合は「インストール」も必要ですしねぇ。

前提条件や癖やそのあたりが「呑み込めそう」なら、gettextを使ってみるとよいんじゃないかなぁ、と思います。

xgettextとか面白いんだけどなぁ……「テンプレートエンジンを使う」前提だと、幾分、ハードルが上がったり諸々が以下略。


自力で作る場合は、おおむね

・辞書のフォーマットを決める

・辞書ファイルを必要言語数だけ作る

テンプレートに辞書ファイルをぶつけて出力する

といった感じ。


んと……ざっぱに、例。

例えば辞書ファイルを「コード: 翻訳文」とします。

日本語と英語を用意してみませう。


辞書.ja

login_button: ログイン

login_text: こちらからログインしてください。

password_reminder_text: パスワードがわからない場合はこのボタンを押してください。

password_reminder_button: パスワードリマインダ


辞書.en

login_button: login

login_text: Please log in from here.

password_reminder_text: Please press this button if you do not know the password.

password_reminder_button: Password reminder

*4


いろいろと面倒なんでいったんSmarty前提。

まずPHP本体側で、「言語にそった辞書」を渡します。

辞書は、key=コード、value=翻訳文、のhash配列方式で情報があると仮定

// 辞書の選択
if (日本語圏なら) {
  $辞書配列 = 日本語の辞書;
} else if (英語圏なら) {
  $辞書配列 = 英語の辞書;
} else {
    // 例外でもぶん投げるかねぇ
}

// 辞書をアサイン
$smarty_obj->assign('dic', $辞書配列);

んで、Smartyでは、例えばこんな風に記述します。

{$div.login_text}
<form ...>


<button>{$dic.login_button}</button>
</form>

こんな風にしてテンプレートに「一切出力用の自然言語を書かずに」辞書ファイルに追い出すと、まぁいろいろとできたり出来たりします。ざっくりとは。


結論

がっつりと多言語対応って、割と案件数的にも少ない気がするので。

調べると、案外とネットの情報も少ないんですよねぇ……なので、書いてみた。


なんかほかにも手法ありそうなのですが、まずは「こんなのもあるよ〜」的に。

突っ込みとかあったら突っ込んでくださいませ > 諸氏

*1UTF-7? なにそれ美味しいの?

*2:政敵って変換するFEPについてどう思う?

*3:まっとうな

*4:英訳への突っ込みはこれを禁止するwww

2017-03-14

[][]大きなIDをどうやってPHPからMySQLに渡す?:発端とまとめ

あるタイミングで、プリペアドステートメント回りのお話と、IDのカラム(によって決まる最大値)のお話と、intサイズのお話が別々に来た時に……頭ん中で混ざって、「あれ?」と思った事があったので調べてみた、って感じになります。


まず話に出ていたのが「AUTO_INCREMENTのカラムの型」のお話。

もうちょっと突っ込むと「intだと足りなくなる瞬間が以下略」。2147483647、ようは21億ちょい、なので、動かし方によっては十分に手が届いちゃう範囲。

int unsignedにしても4294967295だし、そもそもAUTO_INCREMENTなんで負の値いらないからまぁ「bigint unsignedだよねぇ」ってお話があって、これが素材その1。


bitintは8バイトなので、unsignedだと18446744073709551615とかいう、クソ怪しい値までOK……なんだけど。

PHPって、64bit環境の場合、あの子「signed intまでしか整数扱えない(以降は浮動小数点で扱う)」なので………あれ? 9223372036854775807 まではいいんだが、9223372036854775808以降は?

これが素材2。


PDOのプリペアドステートメントは「第二引数変数の型と第三引数の定数の両方がINT」以外はstring扱いだよねぇ、って話が、全然別の流れで普通に出てきて。

………あれ? 「PHP的にintで扱えない値はどうなるの?」って疑問がむくむくと湧いてきて。

これが素材3。


というわけで、素材1〜3までを合成………するまえに。

とりあえず、最低限のチェックから。

<?php

// PHP_INT_MAXの「次の数」の確認
$i = PHP_INT_MAX;
$i += 1;
echo "---\n";
var_dump($i);
printf("%f\n", $i);
echo "---\n";
var_dump((int)$i);
var_dump(intval($i));
echo "---\n";
printf("%d\n", (int)$i);
printf("%d\n", intval($i));
echo "---\n";


// 少し雑に「大きな数」の確認(INT_MAXの先頭に1追加した数値)
$i = 19223372036854775807;
echo "---\n";
var_dump($i);
printf("%f\n", $i);
echo "---\n";
var_dump((int)$i);
var_dump(intval($i));
echo "---\n";
printf("%d\n", (int)$i);
printf("%d\n", intval($i));
echo "---\n";

---

float(9.2233720368548E+18)

9223372036854775808.000000

---

int(-9223372036854775808)

int(-9223372036854775808)

---

-9223372036854775808

-9223372036854775808

---

---

float(1.9223372036855E+19)

19223372036854775808.000000

---

int(776627963145224192)

int(776627963145224192)

---

776627963145224192

776627963145224192

---

うわぁい(苦笑

intでキャストもintval関数も、「負の値に行ったり(これはまぁわかる)」、よくわからん数値になってきたり(多分単純にビットあふれが切り捨てられてる)。

まぁ、マニュアルにも書いてあるしなぁ


http://php.net/manual/ja/function.intval.php

最大値はシステムに依存します。32 ビットシステムでは、 最大の符号付き整数の範囲 -2147483648 〜 2147483647 となります。 このため、そのようなシステムでは intval('1000000000000') は 2147483647 を返します。 64 ビットシステムにおける最大の符号付き整数は 9223372036854775807 となります。


なので、大きなIDを

・受け取って

・INTでキャストして

プレースホルダにバインド

すると、多分間違いなくなんとなくまずもっておそらく「NGであろうなぁ」と思われるに至り、これは「実験せにゃなぁ」と思ったわけでございます。


で…細かい実験は長いんで、後で書きますが。

ものすごく端的に要約すると、現時点のおいちゃん見解としては、大体以下の通りかなぁ、と。

・(IDなんで演算とかしない前提で)文字列で受け取り、文字列で渡すようにする

・WHERE句のIDに「明示的にCASTするかどうか」は、お好みで。可能性として「CASTしておいたほうが効率が良い、かも、しれない」のと「何となくせめて明示したい」w

・IDはvalidateする。ctype_digit()関数がよいと思う

・「文字列から数値への、WHERE句での使用時の暗黙の(またはCASTによる)変換」の挙動が変わらないように、祈るw


いやまぁ「例えば、bigint unsignedを避ける(bigint signedにする)」+「PHPは64bit環境」って選択肢もないわけではないのですがw

まぁ少ないとは思うのですが、サービスの余命考えた時に「どっちかねぇ?」って感じになると思うのです。


いやまぁ実際、現時点のおいちゃん見解も「ど〜かねぇ?」とは思うのですが。暗黙の変換の、しかも「明記されているわけではない」挙動に頼る、ってのも(CAST使えば"暗黙"ではないですが一応)。

ただ、現状、それ以外に今一つ「よいアイデア」が浮かばないんですよねぇ……というわけで、現状における「苦肉の策」だと思っていただけると。と。

なので。「普通の整数を扱う」ときはちゃんとINTで扱って、ただ「INT_MAXを超える可能性があり」かつ「算術演算が発生しない」AUTO_INCREMENTなIDについて」のみ、例外的に、上述のような方法を取らざるを得ないのかなぁ、という。


もうちょっと妙手があればなぁ、と思うので、コメントなどありましたら、お気軽によろしくお願いいたします!!


[][]大きなIDをどうやってPHPからMySQLに渡す?:詳細:MySQL

とりあえず「思考の流れと調査の流れ」をそのまま書くので、読みにくいのはごめんなさいまし。


まずはいくつか検証テーブルを用意…する前に、MySQL自体の動きの確認を。

直近、参考にさせていただいたのは、hnwさんのこちらのサイト。

http://d.hatena.ne.jp/hnw/20120405


例題にある、以下のSQLを実際に流して、動きの確認をしました。

確認したのは、Server version: 5.6.10。ちょいと古いんだけど、ちょうど年末の整理でつぶす直前のサーバだったので、気兼ねなくw

create table decimal_test(id integer auto_increment primary key, a decimal(20));

insert into decimal_test(a) values(9007199254740992),(9007199254740993),(9007199254740994),(9007199254740995),(9007199254740996);

select * from decimal_test where a='9007199254740993';

mysql> select * from decimal_test where a='9007199254740993';

+----+------------------+

| id | a |

+----+------------------+

| 1 | 9007199254740992 |

| 2 | 9007199254740993 |

+----+------------------+

2 rows in set (0.00 sec)

うん、記述通り。


create table bigint_test(id integer auto_increment primary key, a bigint);

insert into bigint_test(a) values(9007199254740992),(9007199254740993),(9007199254740994),(9007199254740995),(9007199254740996);

select * from bigint_test where a='9007199254740993';

mysql> select * from bigint_test where a='9007199254740993';

+----+------------------+

| id | a |

+----+------------------+

| 2 | 9007199254740993 |

+----+------------------+

1 row in set (0.00 sec)

こちらも記述通り。


とりあえず、考察の前提が「AUTO_INCREMENTでの値」のお話なんで

・計算はしない

・型はint系(っていうかぶっちゃけるとbigint unsigned一択)

で、以降お話をすすめます。


さて。

MySQLマニュアルにも、これに近しいお話は書いてありまして。

文字列カラムと数字との比較では、MySQL はカラム上のインデックスを使用して、値をすばやく検索できません。str_col がインデックスの付いた文字列カラムである場合は、次のステートメントで検索を実行するときに、そのインデックスを使用できません。

-略-

その理由は、'1'、' 1'、'1a' のように、値 1 に変換できるさまざまな文字列があるためです。

このような数字は不正確であるため、浮動小数点数 (または浮動小数点数に変換される値) を使用する比較は概算になります。これにより、整合性のない結果が表示される可能性があります。

ってなわけで、書いてある内容を試してみます。


書いてあるのは

mysql> SELECT '18015376320243458' = 18015376320243458;

-> 1

mysql> SELECT '18015376320243459' = 18015376320243459;

-> 0

実験。

mysql> SELECT '18015376320243458' = 18015376320243458;

+-----------------------------------------+

| '18015376320243458' = 18015376320243458 |

+-----------------------------------------+

| 1 |

+-----------------------------------------+

1 row in set (0.01 sec)

mysql> SELECT '18015376320243459' = 18015376320243459;

+-----------------------------------------+

| '18015376320243459' = 18015376320243459 |

+-----------------------------------------+

| 1 |

+-----------------------------------------+

1 row in set (0.00 sec)

………まてこら。話が違う!!

ただ、実はちょいと別の角度から、怪しい動きをします。

数値をちょいと変えるよ & 大体「カラム=値」で聞くのと、カラムのほうが数値型なので、順番も入れ替え。


mysql> SELECT 9223372036854775808 = '9223372036854775808';

+---------------------------------------------+

| 9223372036854775808 = '9223372036854775808' |

+---------------------------------------------+

| 1 |

+---------------------------------------------+

1 row in set (0.00 sec)

mysql> SELECT 9223372036854775808 = '9223372036854775809';

+---------------------------------------------+

| 9223372036854775808 = '9223372036854775809' |

+---------------------------------------------+

| 1 |

+---------------------------------------------+

1 row in set (0.00 sec)

mysql> SELECT 9223372036854775808 = '9223372036854775810';

+---------------------------------------------+

| 9223372036854775808 = '9223372036854775810' |

+---------------------------------------------+

| 1 |

+---------------------------------------------+

1 row in set (0.00 sec)


ほら(悪い方に)あたった。丸め誤差出てる。

ほかのすべてのケースでは、引数浮動小数点 (実) 数として比較されます。

って記述があるので、大体状況の予想はできる。


…んと

mysql> SELECT '9223372036854775808' + 0;

+---------------------------+

| '9223372036854775808' + 0 |

+---------------------------+

| 9.223372036854776e18 |

+---------------------------+

1 row in set (0.08 sec)

mysql> SELECT '9223372036854775809' + 0;

+---------------------------+

| '9223372036854775809' + 0 |

+---------------------------+

| 9.223372036854776e18 |

+---------------------------+

1 row in set (0.00 sec)

mysql> SELECT '9223372036854775810' + 0;

+---------------------------+

| '9223372036854775810' + 0 |

+---------------------------+

| 9.223372036854776e18 |

+---------------------------+

1 row in set (0.00 sec)

こーゆー感じ。

倍精度ったって限界はあるので。このあたりの数値で「1違う」値って、誤差で吸収されて「同じ値」になっちゃったりするのだよねぇ。

………じゃぁ「select * from bigint_test where a='9007199254740993';」がうまくいった、丸め誤差に巻き込まれなかった理由はなにか?


確定できるわけではないのですが、こんな風に組み替えると、こんな風に結果が変わります。

mysql> SELECT 9223372036854775808 = CAST('9223372036854775808' as UNSIGNED);

+---------------------------------------------------------------+

| 9223372036854775808 = CAST('9223372036854775808' as UNSIGNED) |

+---------------------------------------------------------------+

| 1 |

+---------------------------------------------------------------+

1 row in set (0.02 sec)

mysql> SELECT 9223372036854775808 = CAST('9223372036854775809' as UNSIGNED);

+---------------------------------------------------------------+

| 9223372036854775808 = CAST('9223372036854775809' as UNSIGNED) |

+---------------------------------------------------------------+

| 0 |

+---------------------------------------------------------------+

1 row in set (0.00 sec)

mysql> SELECT 9223372036854775808 = CAST('9223372036854775810' as UNSIGNED);

+---------------------------------------------------------------+

| 9223372036854775808 = CAST('9223372036854775810' as UNSIGNED) |

+---------------------------------------------------------------+

| 0 |

+---------------------------------------------------------------+

1 row in set (0.00 sec)


なんとなし「実数(浮動小数点)変換」のケースと「整数変換」のケースがあるんじゃないかなぁ、という予想が、少しばっかり*1

上述を踏まえて、少し「実際にテーブル作って」挙動の確認をしてみたいかなぁ、と思います。


下準備。INSERT、まぁしないのですが「文字と数値」で、両方の値を入れてみましょう(少し蛇足な実験w)。

あとは、本来的にターゲットであるAUTO_INCREMENT。

DROP TABLE IF EXISTS test;

CREATE TABLE test (

i bigint unsigned

)ENGINE=InnoDB;

INSERT INTO test VALUES (9223372036854775806),(9223372036854775807),(9223372036854775808),(9223372036854775809),(9223372036854775810);

INSERT INTO test VALUES ('9223372036854775806'),('9223372036854775807'),('9223372036854775808'),('9223372036854775809'),('9223372036854775810');


DROP TABLE IF EXISTS test_auto;

CREATE TABLE test_auto (

i bigint unsigned NOT NULL AUTO_INCREMENT,

PRIMARY KEY (i)

)ENGINE=InnoDB auto_increment = 9223372036854775806;

INSERT INTO test_auto VALUES (NULL), (NULL), (NULL), (NULL), (NULL), (NULL);


下確認

mysql> SELECT * FROM test;

+---------------------+

| i |

+---------------------+

| 9223372036854775806 |

| 9223372036854775807 |

| 9223372036854775808 |

| 9223372036854775809 |

| 9223372036854775810 |

| 9223372036854775806 |

| 9223372036854775807 |

| 9223372036854775808 |

| 9223372036854775809 |

| 9223372036854775810 |

+---------------------+

10 rows in set (0.00 sec)

すんなりと入ってます。


さて、比較演算子を実験。

mysql> SELECT * FROM test WHERE i = 9223372036854775809;

+---------------------+

| i |

+---------------------+

| 9223372036854775809 |

| 9223372036854775809 |

+---------------------+

2 rows in set (0.00 sec)


mysql> SELECT * FROM test WHERE i = '9223372036854775809';

+---------------------+

| i |

+---------------------+

| 9223372036854775809 |

| 9223372036854775809 |

+---------------------+

2 rows in set (0.00 sec)

mysql> SELECT * FROM test_auto WHERE i = 9223372036854775809;

+---------------------+

| i |

+---------------------+

| 9223372036854775809 |

+---------------------+

1 row in set (0.00 sec)


mysql> SELECT * FROM test_auto WHERE i = '9223372036854775809';

+---------------------+

| i |

+---------------------+

| 9223372036854775809 |

+---------------------+

1 row in set (0.00 sec)

ふむぅ。

単純に「SELECTに比較演算子を与えて出力」ん時は丸め誤差巻き込んでtrue返してたのに、実際のWHERE句になると挙動が違う。

どっちかってぇと「CASTした時」に近いような印象を受ける感じですねぇ。


まぁ、ここについてはhnwさんのほうでも

マッチしたのは1レコードだけでした。このことから、BIGINT型の値と文字列リテラルとを比較した場合は64ビット整数として比較されていることがわかります。

-壮絶に中略-

MySQLの数値型と文字列型の比較は浮動小数点数比較になるようにマニュアルに書いてありますが、整数比較になる例外ケースがいくつか見つかりました。具体的には、BIGINT型と文字列リテラルインデックスつきのDECIMAL型と文字列リテラル、という2組が整数比較されているとわかりました。

って書かれてますし。

細かい話をすると「WHERE句だけなんかなぁ?」とも思うのですが、まぁ、使うし使いたいのはWHERE句で、なんで、気にしないw


また、MySQLマニュアルのほうにも

https://dev.mysql.com/doc/refman/5.6/ja/numeric-type-overview.html

BIGINT カラムについて注意の必要な点は、次のとおりです。

-中略-

文字列を使用して格納すると、いつでも正確な整数値を BIGINT カラムに格納できます。この場合、MySQL は、中間倍精度表現を含まない文字列から数値に変換します。

とあるので。

上述は「格納」ですが、比較についてもなんとなし「中間倍精度表現を含まない文字列から数値に変換」ってロジックが流れている可能性が想起されるような気が、びしばしとするのでございます。


このあたりを踏まえたうえで、じゃぁ次は「PHPからcallするとどうなるの?」っていう、疑問のメインをあさってみたいと思います。


[][]大きなIDをどうやってPHPからMySQLに渡す?:詳細:PHPからcall編

とりあえず、ざっくりと接続してデータをfetchAllでゲトって、「ゲトれる事」をほんのりと確認してみましょう。

基本になるコードは以下の通り。

移行、SQL以降部分だけ適宜すりかえながらやっていきます。


<?php

// DB接続
require_once('config.php');
$dsn = 'mysql:dbname=test;host=localhost;charset=utf8mb4';

// 接続オプションの設定
$opt = array (
    PDO::ATTR_EMULATE_PREPARES => false,
);
// 「複文禁止」が可能なら付け足しておく
if (defined('PDO::MYSQL_ATTR_MULTI_STATEMENTS')) {
    $opt[PDO::MYSQL_ATTR_MULTI_STATEMENTS] = false;
}
// 接続
try {
    $dbh = new PDO($dsn, $user, $pass, $opt);
} catch (PDOException $e) {
    // XXX 本当はもう少し丁寧なエラーページを出力する
    echo 'システムでエラーが起きました';
    exit;
}
//var_dump($dbh);

// プリペアド
$sql = 'SELECT * FROM test;';
$pre = $dbh->prepare($sql);

// バインド
// XXX

// 実行
$r = $pre->execute(); // XXX

// データをまとめて取得
$data = $pre->fetchAll(PDO::FETCH_ASSOC);
var_dump($data);

結果

[gallu@hogehoge48 ~]$ php t.php

array(10) {

[0]=>

array(1) {

["i"]=>

int(9223372036854775806)

}

[1]=>

array(1) {

["i"]=>

int(9223372036854775807)

}

[2]=>

array(1) {

["i"]=>

string(19) "9223372036854775808"

}

[3]=>

array(1) {

["i"]=>

string(19) "9223372036854775809"

}

[4]=>

array(1) {

["i"]=>

string(19) "9223372036854775810"

}

[5]=>

array(1) {

["i"]=>

int(9223372036854775806)

}

[6]=>

array(1) {

["i"]=>

int(9223372036854775807)

}

[7]=>

array(1) {

["i"]=>

string(19) "9223372036854775808"

}

[8]=>

array(1) {

["i"]=>

string(19) "9223372036854775809"

}

[9]=>

array(1) {

["i"]=>

string(19) "9223372036854775810"

}

}

…………ほぉ。

戻り値がintだったりstringだったり。

面倒なんで省略しますが、test_autoテーブルも同じ挙動だったので、おそらく「INT_MAXを超える値の場合、値がぐらつかないように、stringで持ってくる」的なギミックがあるんだろうなぁ、と推測。


さて。

実際の動きとしては、大体

・テーブルからID(とそれ以外のデータの塊)を引っ張ってくる

Pageが存在して、それとは別に、大体IDを引数にして

・IDからテーブルを引っ張ってくる

的な動きをするケースってのは、捨て値で売りさばいても豪邸が立つくらいに大量に頻出する処理かと思われます。

「面倒だし興味あるから、一端、全体をなめたい」ので、foreachを使って、全体をなめてみます。

ちょいと全体が長いので、レコード件数の少ないtest_autoでテストしますが、testテーブルでも同じ結果でした。

まずは、一般的に推奨されていると思われる「暗黙の変換が起きないように、適切にintでキャストしてINT指定する」パターン。

// プリペアド
//$sql = 'SELECT * FROM test;';
$sql = 'SELECT * FROM test_auto;';
$pre = $dbh->prepare($sql);

// バインド
// XXX

// 実行
$r = $pre->execute(); // XXX

// データをまとめて取得
$data = $pre->fetchAll(PDO::FETCH_ASSOC);
//var_dump($data);

//$sql = 'SELECT * FROM test WHERE i = :i;';
$sql = 'SELECT * FROM test_auto WHERE i = :i;';
$pre = $dbh->prepare($sql);
foreach($data as $datum) {
var_dump($datum['i']);
    // バインド
    $pre->bindValue(':i', (int)$datum['i'], PDO::PARAM_INT);

    // 実行
    $r = $pre->execute(); // XXX

    // 取得して出力
    var_dump( $pre->fetchAll(PDO::FETCH_ASSOC) );
}

結果

[gallu@hogehoge48 ~]$ php t.php

int(9223372036854775806)

array(1) {

[0]=>

array(1) {

["i"]=>

int(9223372036854775806)

}

}

int(9223372036854775807)

array(1) {

[0]=>

array(1) {

["i"]=>

int(9223372036854775807)

}

}

string(19) "9223372036854775808"

array(1) {

[0]=>

array(1) {

["i"]=>

int(9223372036854775807)

}

}

string(19) "9223372036854775809"

array(1) {

[0]=>

array(1) {

["i"]=>

int(9223372036854775807)

}

}

string(19) "9223372036854775810"

array(1) {

[0]=>

array(1) {

["i"]=>

int(9223372036854775807)

}

}

string(19) "9223372036854775811"

array(1) {

[0]=>

array(1) {

["i"]=>

int(9223372036854775807)

}

}

ん……わかりやすく「切られてる」感じですねぇ。丸め誤差で丸められちゃってるんだか、INT_MAXで切られてるんだか。

いずれにしても、意図しているレコードではないレコード引っ張ってきちゃってるの感じ。

クエリログ覗いてみませう。


11064 Prepare SELECT * FROM test_auto WHERE i = ?

11064 Close stmt

11064 Execute SELECT * FROM test_auto WHERE i = 9223372036854775806

11064 Execute SELECT * FROM test_auto WHERE i = 9223372036854775807

11064 Execute SELECT * FROM test_auto WHERE i = 9223372036854775807

11064 Execute SELECT * FROM test_auto WHERE i = 9223372036854775807

11064 Execute SELECT * FROM test_auto WHERE i = 9223372036854775807

11064 Execute SELECT * FROM test_auto WHERE i = 9223372036854775807

11064 Close stmt

………あぁあ orz

切られてる切られてる。ドきっちり、切られてる(INT_MAXで切られてるのか丸め誤差で丸められてるのかは不明。アウトな時点で、それ以上の興味がわかなかったのでw)。

可能性としては

・指数表記で出力されてデータが取れない

・INT_MAXで切られる

丸め誤差に従って適切(適当)に丸められる

のあたりを予見していたので、まぁ予想通りっちゃぁ予想どおり。


お次。「文字列による暗黙の変換」を試してみる。

一応丁寧に「第二引数をstringでキャスト」+「第三引数に明示的にSTRを指定」。

// プリペアド
//$sql = 'SELECT * FROM test;';
$sql = 'SELECT * FROM test_auto;';
$pre = $dbh->prepare($sql);

// バインド
// XXX

// 実行
$r = $pre->execute(); // XXX

// データをまとめて取得
$data = $pre->fetchAll(PDO::FETCH_ASSOC);
//var_dump($data);

//$sql = 'SELECT * FROM test WHERE i = :i;';
$sql = 'SELECT * FROM test_auto WHERE i = :i;';
$pre = $dbh->prepare($sql);
foreach($data as $datum) {
var_dump($datum['i']);
    // バインド
    //$pre->bindValue(':i', (int)$datum['i'], PDO::PARAM_INT);
    $pre->bindValue(':i', (string)$datum['i'], PDO::PARAM_STR);

    // 実行
    $r = $pre->execute(); // XXX

    // 取得して出力
    var_dump( $pre->fetchAll(PDO::FETCH_ASSOC) );
}

結果

[gallu@hogehoge48 ~]$ php t.php

int(9223372036854775806)

array(1) {

[0]=>

array(1) {

["i"]=>

int(9223372036854775806)

}

}

int(9223372036854775807)

array(1) {

[0]=>

array(1) {

["i"]=>

int(9223372036854775807)

}

}

string(19) "9223372036854775808"

array(1) {

[0]=>

array(1) {

["i"]=>

string(19) "9223372036854775808"

}

}

string(19) "9223372036854775809"

array(1) {

[0]=>

array(1) {

["i"]=>

string(19) "9223372036854775809"

}

}

string(19) "9223372036854775810"

array(1) {

[0]=>

array(1) {

["i"]=>

string(19) "9223372036854775810"

}

}

string(19) "9223372036854775811"

array(1) {

[0]=>

array(1) {

["i"]=>

string(19) "9223372036854775811"

}

}

うんこっちだと取れる。


クエリログ確認

11069 Prepare SELECT * FROM test_auto WHERE i = ?

11069 Close stmt

11069 Execute SELECT * FROM test_auto WHERE i = '9223372036854775806'

11069 Execute SELECT * FROM test_auto WHERE i = '9223372036854775807'

11069 Execute SELECT * FROM test_auto WHERE i = '9223372036854775808'

11069 Execute SELECT * FROM test_auto WHERE i = '9223372036854775809'

11069 Execute SELECT * FROM test_auto WHERE i = '9223372036854775810'

11069 Execute SELECT * FROM test_auto WHERE i = '9223372036854775811'

11069 Close stmt

うん、こんな感じだよねぇ………暗黙の変換が気になる気になるw


一応、軽く無駄な抵抗を試みてみる。

$sql = 'SELECT * FROM test_auto WHERE i = cast(:i as UNSIGNED);';

ってやって、値を「明示的にキャスト」してみる。してみるだけ。


結果

[gallu@hogehoge48 ~]$ php t.php

int(9223372036854775806)

array(1) {

[0]=>

array(1) {

["i"]=>

int(9223372036854775806)

}

}

int(9223372036854775807)

array(1) {

[0]=>

array(1) {

["i"]=>

int(9223372036854775807)

}

}

string(19) "9223372036854775808"

array(1) {

[0]=>

array(1) {

["i"]=>

string(19) "9223372036854775808"

}

}

string(19) "9223372036854775809"

array(1) {

[0]=>

array(1) {

["i"]=>

string(19) "9223372036854775809"

}

}

string(19) "9223372036854775810"

array(1) {

[0]=>

array(1) {

["i"]=>

string(19) "9223372036854775810"

}

}

string(19) "9223372036854775811"

array(1) {

[0]=>

array(1) {

["i"]=>

string(19) "9223372036854775811"

}

}


クエリログ

11077 Prepare SELECT * FROM test_auto WHERE i = cast(? as UNSIGNED)

11077 Close stmt

11077 Execute SELECT * FROM test_auto WHERE i = cast('9223372036854775806' as UNSIGNED)

11077 Execute SELECT * FROM test_auto WHERE i = cast('9223372036854775807' as UNSIGNED)

11077 Execute SELECT * FROM test_auto WHERE i = cast('9223372036854775808' as UNSIGNED)

11077 Execute SELECT * FROM test_auto WHERE i = cast('9223372036854775809' as UNSIGNED)

11077 Execute SELECT * FROM test_auto WHERE i = cast('9223372036854775810' as UNSIGNED)

11077 Execute SELECT * FROM test_auto WHERE i = cast('9223372036854775811' as UNSIGNED)

11077 Close stmt

あぁよかったここにはプリペアド効くんだ。

効率の良し悪しは不明。ただまぁ「暗黙に変換される」よりは、まだしも「明示的な変換」のほうが、1mmほどはマシなんじゃなかろうかなぁ、っと。


とりあえずここまでで

・INT_MAXを超える値を、intでキャストするとちゃんと動かなくなる

状態が見えてきたので。

そうすると、STRING一択、(暗黙または明示による、SQL側での文字→数値)変換一択、しかとりあえず選択肢ないかなぁ、的な。


で、そうすると後は外部から入ってくるIDの、最低限のvalidate。

これについては、おいちゃんは ctype_digit() 関数がぴったり、だと思う。

注意点がないでもないんだけど。それについては「引数を明示的にstringでキャストすればいいじゃない」って思ってるw。そもそも「引数はstring」って明示されてるモノなんだし。

ちゃんとその辺を気にすると、PHP7に行った時に楽よ?w


その辺を踏まえての、DB接続以降の「ID受け取って1件取り出す」側の、サンプルっぽいブツ。

// データを取得したっぽい処理
$id = '9223372036854775810';
//$id = '92233720hoge'; // NGな値

// 超絶ざっくりvalidate
if (false === ctype_digit((string)$id)) {
    echo 'おかしなIDです';
    exit;
}

//$sql = 'SELECT * FROM test WHERE i = :i;';
$sql = 'SELECT * FROM test_auto WHERE i = cast(:i as UNSIGNED);';
$pre = $dbh->prepare($sql);

// バインド
$pre->bindValue(':i', (string)$id, PDO::PARAM_STR);

// 実行
$r = $pre->execute(); // XXX

// 取得して出力
var_dump( $pre->fetchAll(PDO::FETCH_ASSOC) );

とりあえず、こんな感じかなぁ、と。

*1:余談ですが。CASTで「実数への変換」って、ないんですかねぇ? いや「ちゃんとマニュアル読み切れてないだけ」な気もするのですが

2017-03-13

[][]異字体

大本ネタはこちら。

http://moriyoshi.hatenablog.com/entry/2017/03/13/011005

ここで「異体字セレクタ」ってのをはじめて知って、ちょろっと調べものをしたので、備忘録的に。

UnicodeっつかUTF-8で「1文字づつ」に分割するごにょごにょを書きたいかもなぁ、とか、ちょろっと妄想をしていたので。


とりあえず。

(二点しんにょう U+8FBB + VS18) ⇔「辻??」(一点しんにょう U+8FBB + VS17)

……ふお?

U+8FBB はわかるんだが、 + VS18、って、なにかしらん?

が、startライン。


http://itpro.nikkeibp.co.jp/article/COLUMN/20110124/356398/?rt=nocnt

見ると、「E9 82 8A F3 A0 84 80」とか「E9 82 8A F3 A0 84 81」とか。


念の為に、UTF-8エンコーディング方法を確認。

https://ja.wikipedia.org/wiki/UTF-8

………ぶも?

5バイト長とか6バイト長とかある???

一端より道して調べもの。…いやもしあるんなら。MySQLのutf8mb4が、些か、怖いやもしれぬので。


https://ja.wikipedia.org/wiki/Unicode%E4%B8%80%E8%A6%A7%E8%A1%A8

http://www.unicode.org/roadmaps/


ふむ。第16面でも「U+F0000 〜 U+10FFFF」なのねん。

これだと十分に4バイトで入る。…5バイト長とか6バイト長は「将来に向けての布石」なのかしらん?

とりあえず「当面の厄介」は消えたぽいので、「5バイト長とか6バイト長」の調査は一端ここで打ち切り


もどして…とりあえず、ビット列に分解して、有効な値を取り出してみる。


E9 82 8A F3 A0 84 80

1110 1001 1000 0010 1000 1010 1111 0011 1010 0000 1000 0100 1000 0000


E9 82 8A F3 A0 84 81

1110 1001 1000 0010 1000 1010 1111 0011 1010 0000 1000 0100 1000 0001


使う子だけ、はじき出す…とりあえず先頭は多分3バイトのはず。

1110 yyyy 10yx xxxx 10xx xxxx

1110 1001 1000 0010 1000 1010 1111 0011 1010 0000 1000 0100 1000 0000

1110 1001 1000 0010 1000 1010 1111 0011 1010 0000 1000 0100 1000 0001


どっちも

1001000010001010

908A


うん確かに「邊」の字だ。

てことは手前3バイトは文字なんだ…後ろの4バイトを解析してみませう。

先頭が11110なんで、UTF-8的には「4バイト文字」のはず。


1111 0yyy 10yy xxxx 10xx xxxx 10xx xxxx

1111 0011 1010 0000 1000 0100 1000 0000

1111 0011 1010 0000 1000 0100 1000 0001


011100000000100000000

011100000000100000001


E0100

E0101


一覧を見てみる…

https://ja.wikipedia.org/wiki/Unicode%E4%B8%80%E8%A6%A7_E0000-E0FFF

「VS17」とか「VS18」とか。


あぁ!!

単純に、ここらへん(おそらく、E0100〜E01E0F)までは「異字体(IVS:Ideographic Variation Sequence)だよ!!」って取決めなのか!!

うん、腑に落ちた。


ってことは「1文字を切り出す」時に、もし後ろにこの子がいたら「前の字とセットで」持っておいてあげる必要があるんだなぁ。

PHPの実装とかどうなってるんだろ?

まぁその辺はまた後日、気になったら調べてみませうw

2014-05-11

[]MySQLのmemcachedインターフェース(InnoDB Integration with memcached)を実験してみた:概要

ほぼ完全に備忘録な上に「やった手順に沿って」の内容なんで、激しく長いです(苦笑


先に結論だけ書いておくと

・格納先テーブルは任意に選べる

keyについては…

 *charとvarcharあたりが相性がいいんだけど、intもいける

 *varbinaryが相性悪くて、故に「'A' == 'a'」「'D1' == 'd1'」が成り立つ状況がちょっと怖い

 *以外なことにintがいける

 *datetimeとかはまぁ予想通りNG

 *keyの値のうち、スペース(0x20)はアンダースコアに自動変換される。ために、元々のデータに「スペースを含むkeyの値」がある場合、取得不可能になるので注意

 *アクセス用の名前(innodb_memcache.containers.name)と値とのセパレータに.(ドット)が用いられているため、keyカラム名にはドットを入れないようにする。値に混入した場合にどうなるかは未調査

・valueについては…

 *単一カラムの場合、char、varchar、varbinary、text、blob、intが利用可能。datetime系は利用不可

 *intは使えるけど、戻ってくる値がstringになってるので、微妙に注意

 *実は「複数カラム」が可能。各カラムのデータは | によって仕切られる。仕切り文字の変更が可能かどうかは不明*1。また、エスケープなどが存在するかは不明。なので、データに「 | が含まれないこと」を保証する必要がありそう

 *valueを「複数カラム」指定することも可能だが、その場合、varbinaryがまざると使えなくなる

 *日本語は問題なく使用可能

 *テーブルを「CHARACTER SET 'binary'」で作ると使用不可になるため、「CHARACTER SET 'utf8mb4'」が無難なところ


あと、未検証だけどいけそうなのが

・多分、複合キーがいけるぽい雰囲気がある


先に雑感。

PKのカラムがintまたはvarcharのテーブルにおいて、いわゆるコードテーブル(マスターテーブル)のreadであれば、利用は比較的容易かもしれない(特に引っ張ってくるべき値が単一カラムの場合)。

それ以外でも「とりあえず見たいだけ」というような、単純なread用途であれば、適宜置き換えるのは、場合によっては有益かもしれない。


おおざっぱに基本

「準備( http://d.hatena.ne.jp/gallu/20140511/p2 )」をすると、innodb_memcache っていうdatabaseが出来るです。

で、ここに

cache_policies

config_options

containers

の3つのテーブルが出来ます。

cache_policiesとconfig_optionsについては、とりあえず「用途不明」。

中を見てるとなんか出来そうなんですが、config_optionsの中身を「軽く変えた」程度だと意図する挙動にはならなかったので、ここは「眺めておく」程度が無難。


んで。

ポイントはcontainersテーブル。

いくつかよく分からんカラムもあるんだけど。

基本的には「(db_schema)にある(db_table)ってテーブルの、(key_columns)をkeyに、(value_columns)をvalueに持ってる」程度の感じ。


テーブルは、一番分かりやすいのを一つ書いておくと、こんな感じ。

create table test1 (
  `key` varchar(256),
  `val` varchar(256),
  `flags` int,
  `cas_column` bigint UNSIGNED,
  `expire_time_column` int,
  PRIMARY KEY(`key`)
);

flags、cas_column、expire_time_columnは一端「おまじない」だと思って頂けると。


このテーブルが、furu_testっていうdatabaseに入っている、と仮定すると。

まず、containersテーブルに、情報をぶちこみます。

insert into innodb_memcache.containers 
   set name='test1', db_schema='furu_test', db_table='test1', key_columns='key', value_columns='val',
       flags='flags', cas_column='cas_column', expire_time_column='expire_time_column', unique_idx_name_on_key='PRIMARY';

nameは「識別用の名前」なんで、ある程度適当に(っても使うから、ある程度は考えて)。

db_schema、db_table、key_columns、value_columnsは前述の通り。

flags、cas_column、expire_time_column、unique_idx_name_on_keyは一端「おまじない」で覚えてください。


そうすると後は

<?php
// 接続
$m = new Memcache();
$r = $m->addServer('localhost', 11211);
// データの取得
$v = $m->get('@@test1.d1');
var_dump($v);
// データの設定
$r = $m->set('@@test1.d1', (string)mt_rand(0,1000));

こんな感じで使えます。

setとかgetとかaddとかの、keyの値を「'@@' + containers.nameの値 + '.' + keyの値」にするのがコツ。


[]MySQLのmemcachedインターフェース(InnoDB Integration with memcached)を実験してみた:準備変

とりあえず、以下を先にやっておきましょう。

・必要なconfigの設定(SQL文が流れる)

cat ./plugin/innodb_memcached/innodb_memcache/scripts/innodb_memcached_config.sql | mysql -u root

・プラグインの設定(SQL コンソールにて)

install plugin daemon_memcached soname "libmemcached.so";

mysql.plugin に情報が入る(から、以降、restartしても問題ない)


・memcachedは認証が存在しないため、とりあえずiptablesでブロック(本番は、ロードバランサあたりでフィルタリング、が適切)

記述は、iptablesへの設定内容。

-A INPUT -p tcp -m state --state NEW -m tcp --dport 11211 -j DROP

PHPでmemcacheまたはmemecachedが使えるように設定

php.iniとかで適宜。

おいちゃんはこんな手順でやりました。適当に妄想補完してください。

wget https://launchpad.net/libmemcached/1.0/1.0.18/+download/libmemcached-1.0.18.tar.gz
tar zvxf libmemcached-1.0.18.tar.gz
cd libmemcached-1.0.18/
./configure
make

wget http://pecl.php.net/get/memcache-2.2.7.tgz
tar zvxf memcache-2.2.7.tgz
cd memcache-2.2.7
phpize
./configure
make -j 4

echo ini_get('extension_dir');


sudo cp modules/memcache.so /usr/local/lib/php/extensions/no-debug-non-zts-20121212/

php.ini

extension=memcache.so


[]MySQLのmemcachedインターフェース(InnoDB Integration with memcached)を実験してみた:チューニング周りの備忘録

あんまりちゃんと調査してないんで、本気で備忘録程度。


とりあえず、memslapで簡単にベンチマーク確認しておきませう。

# 10,000回のテスト
memslap --servers=localhost --test set
memslap --servers=localhost --test get

以下のパラメタがチューニングと関連あるぽいのですが、全く以て未検証です。

後日、もし気が向いたらなにか記事書き足しますw


daemon_memcached_w_batch_size

daemon_memcached_r_batch_size

innodb_api_trx_level

innodb_api_disable_rowlock

innodb_api_enable_mdl

SELECT @@tx_isolation;


[]MySQLのmemcachedインターフェース(InnoDB Integration with memcached)を実験してみた:備忘録

きちんと把握したい人以外は特に読まなくてもイイ内容w

ただまぁ「きちんとした把握もせずに使う人」が技術者なのか? という問いが、おいちゃん的にはあったりするんだけど、まぁ人それぞれなので。


containersテーブルについて少し

基本的は、前述の通り「(db_schema)にある(db_table)ってテーブルの、(key_columns)をkeyに、(value_columns)をvalueに持ってる」程度の感じ。

あとはflags、cas_column、expire_time_columnは「カラム名の指定」。この3つのカラムが、(db_table)テーブルん中に必要みたい。

とりあえずおいちゃんは

  flags int,
  cas_column bigint UNSIGNED,
  expire_time_column int,

ってやってた。最適解があるかもしれないので、とりあえずこれは「動いたよ〜」程度の、無考察なブツなんでご注意のほどを。


flgasのカラムには、とりあえず0をぶちこんでおくと「未使用」って宣言になるみたい。

https://dev.mysql.com/doc/refman/5.6/en/innodb-memcached-internals.html

flags specifies the columns used as flags (a user-defined numeric value that is stored and retrieved along with the main value) for memcached. It is also used as the column specifier for some operations (such as incr, prepend) if memcached value is mapped to multiple columns. So the operation would be done on the specified column. For example, if you have mapped a value to 3 columns, and only want the increment operation performed on one of these columns, you can use flags to specify which column will be used for these operations. If you do not use the flags column, set its value to 0 to indicate that it is unused.


cas_columnとexpire_time_columnも、一端、0でいいみたい。

https://dev.mysql.com/doc/refman/5.6/en/innodb-memcached-internals.html

cas_column and expiration_time_column are used specifically to store the cas (compare-and-swap) and exp (expiry) value of memcached. Those values are related to the way memcached hashes requests to different servers and caches data in memory. Because the InnoDB memcached plugin is so tightly integrated with a single memcached daemon, and the in-memory caching mechanism is handled by MySQL and the buffer pool, these columns are rarely needed in this type of deployment. If you do not use these columns, set their value to 0 to indicate that the columns are unused.


unique_idx_name_on_keyは…

https://dev.mysql.com/doc/refman/5.6/en/innodb-memcached-internals.html

unique_idx_name_on_key is the name of the index on the key column. It must be a unique index. It can be the primary key or a secondary index. Preferably, make the key column the primary key of the InnoDB table. Doing so saves a lookup step over using a secondary index for this column. You cannot make a covering index for memcached lookups; InnoDB returns an error if you try to define a composite secondary index over both the key and value columns.

ってあるんだけど。実際には'PRIMARY'っていう固定の文字列でいけた。'PRIMARY KEY'でもいけた。

ので…使ってるのかどうか今ひとつ不明。


テストの列挙

先にお断り。

長いです。大量です。長いです。


drop table test1;
create table test1 (
  `key` varbinary(256),
  `val` varbinary(256),
  `flags` int,
  cas_column bigint UNSIGNED,
  expire_time_column int,
  PRIMARY KEY(`key`)
);
insert into innodb_memcache.containers set name='test1', db_schema='furu_test', db_table='test1', key_columns='key', value_columns='val', flags='flags', cas_column='cas_column', expire_time_column='expire_time_column', unique_idx_name_on_key='PRIMARY';
insert into test1 set `key`='d1', val='val', flags=0, cas_column=0, expire_time_column=0;
<?php
// 接続
$m = new Memcache();
$r = $m->addServer('localhost', 11211);
// データの取得
$v = $m->get('@@test1.d1');
var_dump($v);

…うまくいかない。

なんで?(答え:keyがvarbinaryだからなんだが)


create table test2 (
  pkeys varbinary(256) not null,
  val varbinary(256),
  flags int,
  cas_column bigint UNSIGNED,
  expire_time_column int,
  PRIMARY KEY(pkeys)
);
insert into test2 set pkeys='d1', val='val', flags=0, cas_column=0, expire_time_column=0;
insert into innodb_memcache.containers set name='test2', db_schema='furu_test', db_table='test2', key_columns='pkeys', value_columns='val', flags='flags', cas_column='cas_column', expire_time_column='expire_time_column', unique_idx_name_on_key='PRIMARY';
insert into innodb_memcache.containers set name='test2t', db_schema='test', db_table='test2', key_columns='pkeys', value_columns='val', flags='flags', cas_column='cas_column', expire_time_column='expire_time_column', unique_idx_name_on_key='PRIMARY';

とりあえず、keyをnot nullにしてみる。

あと、念のため「database名"test"の中」にテーブルを設置してみる(大抵のサンプルがそこにある前提で書いてあったから念のため)。

………駄目。

なんだろう?


create table test3 (
  pkeys varchar(256) not null,
  val varchar(256),
  flags int,
  cas_column bigint UNSIGNED,
  expire_time_column int,
  PRIMARY KEY(pkeys)
);
insert into test3 set pkeys='d1', val='val', flags=0, cas_column=0, expire_time_column=0;
insert into innodb_memcache.containers set name='test3t', db_schema='test', db_table='test3', key_columns='pkeys', value_columns='val', flags='flags', cas_column='cas_column', expire_time_column='expire_time_column', unique_idx_name_on_key='PRIMARY';
insert into innodb_memcache.containers set name='test3', db_schema='furu_test', db_table='test3', key_columns='pkeys', value_columns='val', flags='flags', cas_column='cas_column', expire_time_column='expire_time_column', unique_idx_name_on_key='PRIMARY';

あ、通った。

なんだ?


create table test4 (
  pkeys varchar(256) not null,
  val varbinary(256),
  flags int,
  cas_column bigint UNSIGNED,
  expire_time_column int,
  PRIMARY KEY(pkeys)
);
insert into test4 set pkeys='d1', val='val', flags=0, cas_column=0, expire_time_column=0;
insert into innodb_memcache.containers set name='test4', db_schema='furu_test', db_table='test4', key_columns='pkeys', value_columns='val', flags='flags', cas_column='cas_column', expire_time_column='expire_time_column', unique_idx_name_on_key='PRIMARY';

もういっちょ同じのを。

…これも通る。

varbinaryイヤなのかしら?

…ってのとは別に、嫌な予感がするのでテスト。

<?php
// 接続
$m = new Memcache();
$r = $m->addServer('localhost', 11211);
// データの取得
$v = $m->get('@@test4.D1');
var_dump($v);

………あぁ通るねぇ orz

varcharだから仕方が無いか orz


create table test5 (
  pkeys char(255) not null,
  val varbinary(256),
  flags int,
  cas_column bigint UNSIGNED,
  expire_time_column int,
  PRIMARY KEY(pkeys)
);
insert into test5 set pkeys='d1', val='val', flags=0, cas_column=0, expire_time_column=0;
insert into innodb_memcache.containers set name='test5', db_schema='furu_test', db_table='test5', key_columns='pkeys', value_columns='val', flags='flags', cas_column='cas_column', expire_time_column='expire_time_column', unique_idx_name_on_key='PRIMARY';

charも通るんだ。



create table test6 (
  pkeys varchar(256) not null,
  val text,
  flags int,
  cas_column bigint UNSIGNED,
  expire_time_column int,
  PRIMARY KEY(pkeys)
);
insert into test6 set pkeys='d1', val='val', flags=0, cas_column=0, expire_time_column=0;
insert into innodb_memcache.containers set name='test6', db_schema='furu_test', db_table='test6', key_columns='pkeys', value_columns='val', flags='flags', cas_column='cas_column', expire_time_column='expire_time_column', unique_idx_name_on_key='PRIMARY';

valにtextも通るねぇ。


create table test7 (
  pkeys varchar(256) not null,
  val blob,
  flags int,
  cas_column bigint UNSIGNED,
  expire_time_column int,
  PRIMARY KEY(pkeys)
);
insert into test7 set pkeys='d1', val='val', flags=0, cas_column=0, expire_time_column=0;
insert into test7 set pkeys='d2', val='0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789', flags=0, cas_column=0, expire_time_column=0;
insert into innodb_memcache.containers set name='test7', db_schema='furu_test', db_table='test7', key_columns='pkeys', value_columns='val', flags='flags', cas_column='cas_column', expire_time_column='expire_time_column', unique_idx_name_on_key='PRIMARY';

…あれ? valがblobでも通るよ?

create table test8 (
  pkeys varbinary(256) not null,
  val blob,
  flags int,
  cas_column bigint UNSIGNED,
  expire_time_column int,
  PRIMARY KEY(pkeys)
);
insert into test8 set pkeys='d1', val='val', flags=0, cas_column=0, expire_time_column=0;
insert into innodb_memcache.containers set name='test8', db_schema='furu_test', db_table='test8', key_columns='pkeys', value_columns='val', flags='flags', cas_column='cas_column', expire_time_column='expire_time_column', unique_idx_name_on_key='PRIMARY';

keyをvarbinaryにしてみる…うんこっちはfalseか orz

どうも、valueにvarbinaryはよいのだけど、keyにするとむずがるみたいだ orz


drop table test9;
create table test9 (
  pkeys varchar(256) not null,
  val int,
  flags int,
  cas_column bigint UNSIGNED,
  expire_time_column int,
  PRIMARY KEY(pkeys)
);
insert into test9 set pkeys='d1', val=100, flags=0, cas_column=0, expire_time_column=0;
insert into test9 set pkeys='d1 2', val=100, flags=0, cas_column=0, expire_time_column=0;
insert into test9 set pkeys='d1_2', val=101, flags=0, cas_column=0, expire_time_column=0;
insert into innodb_memcache.containers set name='test9', db_schema='furu_test', db_table='test9', key_columns='pkeys', value_columns='val', flags='flags', cas_column='cas_column', expire_time_column='expire_time_column', unique_idx_name_on_key='PRIMARY';

2つ実験。

1つめは「valueにintは?」

答え:通る。ただ、getするとstringになってはくるんだが(苦笑

2つめは「keyの中にあるスペースの扱い」。アンダースコアになってしまうために

$v = $m->get('@@test9.d1 2');
var_dump($v);

って指定をしても、戻りは「string(3) "101"」になる。


create table test10 (
  pkeys int not null,
  val int,
  flags int,
  cas_column bigint UNSIGNED,
  expire_time_column int,
  PRIMARY KEY(pkeys)
);
insert into test10 set pkeys=1, val=100, flags=0, cas_column=0, expire_time_column=0;
insert into innodb_memcache.containers set name='test10', db_schema='furu_test', db_table='test10', key_columns='pkeys', value_columns='val', flags='flags', cas_column='cas_column', expire_time_column='expire_time_column', unique_idx_name_on_key='PRIMARY';

keyにint指定したらどうなるんだろう?

答え:無問題。通りました普通に。


create table test11 (
  pkeys int not null,
  val1 int,
  val2 float,
  val3 varchar(256),
  val4 varbinary(256),
  val5 blob,
  val6 datetime,
  flags int,
  cas_column bigint UNSIGNED,
  expire_time_column int,
  PRIMARY KEY(pkeys)
);
insert into test11 set pkeys=1, val1=100, val2=1.123, val3='varchar', val4='binary', val5='blob', val6='2014-1-1 10:22:33', flags=0, cas_column=0, expire_time_column=0;
insert into innodb_memcache.containers set name='test11', db_schema='furu_test', db_table='test11', key_columns='pkeys', value_columns='val1,val2,val3,val4,val5,val6', flags='flags', cas_column='cas_column', expire_time_column='expire_time_column', unique_idx_name_on_key='PRIMARY';
insert into innodb_memcache.containers set name='test11_1', db_schema='furu_test', db_table='test11', key_columns='pkeys', value_columns='val1', flags='flags', cas_column='cas_column', expire_time_column='expire_time_column', unique_idx_name_on_key='PRIMARY';
insert into innodb_memcache.containers set name='test11_2', db_schema='furu_test', db_table='test11', key_columns='pkeys', value_columns='val1,val2', flags='flags', cas_column='cas_column', expire_time_column='expire_time_column', unique_idx_name_on_key='PRIMARY';
insert into innodb_memcache.containers set name='test11_3', db_schema='furu_test', db_table='test11', key_columns='pkeys', value_columns='val1|val2', flags='flags', cas_column='cas_column', expire_time_column='expire_time_column', unique_idx_name_on_key='PRIMARY';

さて…複数カラムの実験。

ん…「name='test11_1'」以外は全部NG。なんだろ?


create table test12 (
  pkeys varchar(256) not null,
  val1 varchar(256),
  val2 varbinary(256),
  val3 blob,
  flags int,
  cas_column bigint UNSIGNED,
  expire_time_column int,
  PRIMARY KEY(pkeys)
);
insert into test12 set pkeys='d1', val1='varchar', val2='binary', val3='blob', flags=0, cas_column=0, expire_time_column=0;
insert into innodb_memcache.containers set name='test12', db_schema='furu_test', db_table='test12', key_columns='pkeys', value_columns='val1', flags='flags', cas_column='cas_column', expire_time_column='expire_time_column', unique_idx_name_on_key='PRIMARY';
insert into innodb_memcache.containers set name='test12_1', db_schema='furu_test', db_table='test12', key_columns='pkeys', value_columns='val1,val2,val3', flags='flags', cas_column='cas_column', expire_time_column='expire_time_column', unique_idx_name_on_key='PRIMARY';
insert into innodb_memcache.containers set name='test12_2', db_schema='furu_test', db_table='test12', key_columns='pkeys', value_columns='val1|val2|val3', flags='flags', cas_column='cas_column', expire_time_column='expire_time_column', unique_idx_name_on_key='PRIMARY';

「name='test12'」だけ…複数カラムって無理なのかしらん?


drop table test13;
create table test13 (
  pkeys varchar(128) not null,
  val1 varchar(256),
  val2 varchar(256),
  val3 varchar(128),
  flags int,
  cas_column bigint UNSIGNED,
  expire_time_column int,
  PRIMARY KEY(pkeys)
) CHARACTER SET 'utf8mb4', ENGINE=InnoDB;
insert into test13 set pkeys='d1', val1='varchar', val2='binary', val3='blob', flags=0, cas_column=0, expire_time_column=0;
insert into innodb_memcache.containers set name='test13', db_schema='furu_test', db_table='test13', key_columns='pkeys', value_columns='val1', flags='flags', cas_column='cas_column', expire_time_column='expire_time_column', unique_idx_name_on_key='PRIMARY';
insert into innodb_memcache.containers set name='test13_1', db_schema='furu_test', db_table='test13', key_columns='pkeys', value_columns='val1,val2,val3', flags='flags', cas_column='cas_column', expire_time_column='expire_time_column', unique_idx_name_on_key='PRIMARY';
insert into innodb_memcache.containers set name='test13_2', db_schema='furu_test', db_table='test13', key_columns='pkeys', value_columns='val1|val2|val3', flags='flags', cas_column='cas_column', expire_time_column='expire_time_column', unique_idx_name_on_key='PRIMARY';

あ、のった。…varbinaryが絡むと駄目なの? エライこと冷遇されてないかい?

「value_columns='val1,val2,val3'」と「value_columns='val1|val2|val3'」、とりあえず、どっちの書式でもOKなんだなぁ。

ちなみに値は「string(19) "varchar|binary|blob"」と一本につながってきます。

ついでに

//
$m->set('@@test13_1.d2', '1|2|3');
//
$v = $m->get('@@test13_1.d2');
var_dump($v);

からの

mysql> select * from test13 \G

                                                      • 1. row ---------------------------

pkeys: d1

val1: varchar

val2: binary

val3: blob

flags: 0

cas_column: 0

expire_time_column: 0

                                                      • 2. row ---------------------------

pkeys: d2

val1: 1

val2: 2

val3: 3

flags: 0

cas_column: 20022

expire_time_column: 0

2 rows in set (0.00 sec)

ってな感じで、ちゃんと「分割しての設定」も出来るようです。


create table test14 (
  pkeys varchar(256) not null,
  val1 varbinary(256),
  val2 varbinary(256),
  flags int,
  cas_column bigint UNSIGNED,
  expire_time_column int,
  PRIMARY KEY(pkeys)
);
insert into test14 set pkeys='d1', val1='varchar', val2='binary', flags=0, cas_column=0, expire_time_column=0;
insert into innodb_memcache.containers set name='test14', db_schema='furu_test', db_table='test14', key_columns='pkeys', value_columns='val1,val2', flags='flags', cas_column='cas_column', expire_time_column='expire_time_column', unique_idx_name_on_key='PRIMARY';

うんvarbinaryはやっぱり駄目なんだ。


create table test15 (
  pkeys int not null,
  val1 varchar(256),
  val2 varchar(256),
  val3 varchar(128),
  flags int,
  cas_column bigint UNSIGNED,
  expire_time_column int,
  PRIMARY KEY(pkeys)
) CHARACTER SET 'utf8mb4', ENGINE=InnoDB;
insert into test15 set pkeys=1, val1='varchar', val2='binary', val3='blob', flags=0, cas_column=0, expire_time_column=0;
insert into innodb_memcache.containers set name='test15', db_schema='furu_test', db_table='test15', key_columns='pkeys', value_columns='val1,val2,val3', flags='flags', cas_column='cas_column', expire_time_column='expire_time_column', unique_idx_name_on_key='PRIMARY';

keyがint」かつ「valueが複数カラム」…ふむ、普通に通る、っと。


create table test17 (
  pkeys varchar(256) not null,
  val1 varchar(256),
  val2 varchar(256),
  val3 varchar(128),
  flags int,
  cas_column bigint UNSIGNED,
  expire_time_column int,
  PRIMARY KEY(pkeys)
) CHARACTER SET 'latin1', ENGINE=InnoDB;
insert into test17 set pkeys=1, val1='varchar', val2='binary', val3='blob', flags=0, cas_column=0, expire_time_column=0;
insert into innodb_memcache.containers set name='test17', db_schema='furu_test', db_table='test17', key_columns='pkeys', value_columns='val1,val2,val3', flags='flags', cas_column='cas_column', expire_time_column='expire_time_column', unique_idx_name_on_key='PRIMARY';

「CHARACTER SET 'latin1'」は通る…latin1はいいんだ。


create table test18 (
  pkeys varchar(256) not null,
  val1 varchar(256),
  val2 varchar(256),
  val3 varchar(128),
  flags int,
  cas_column bigint UNSIGNED,
  expire_time_column int,
  PRIMARY KEY(pkeys)
) CHARACTER SET 'binary', ENGINE=InnoDB;
insert into test18 set pkeys=1, val1='varchar', val2='binary', val3='blob', flags=0, cas_column=0, expire_time_column=0;
insert into innodb_memcache.containers set name='test18', db_schema='furu_test', db_table='test18', key_columns='pkeys', value_columns='val1,val2,val3', flags='flags', cas_column='cas_column', expire_time_column='expire_time_column', unique_idx_name_on_key='PRIMARY';

「CHARACTER SET 'binary'」でbinaryにしてみるとやっぱり嫌がられる。

binaryは一通り、おいやですかしらん?


drop table test19 ;
create table test19 (
  pkeys varchar(256) not null,
  val1 int,
  val2 int,
  val3 int,
  flags int,
  cas_column bigint UNSIGNED,
  expire_time_column int,
  PRIMARY KEY(pkeys)
) CHARACTER SET 'latin1', ENGINE=InnoDB;
insert into test19 set pkeys='d1', val1=10, val2=20, val3=30, flags=0, cas_column=0, expire_time_column=0;
insert into innodb_memcache.containers set name='test19', db_schema='furu_test', db_table='test19', key_columns='pkeys', value_columns='val1,val2,val3', flags='flags', cas_column='cas_column', expire_time_column='expire_time_column', unique_idx_name_on_key='PRIMARY';

valueのintは特に気にしない…っと。

まぁ「string(8) "10|20|30"」ってreturnになるにはしても。


create table test20 (
  pkeys varchar(256) not null,
  val1 text,
  val2 text,
  val3 text,
  flags int,
  cas_column bigint UNSIGNED,
  expire_time_column int,
  PRIMARY KEY(pkeys)
) CHARACTER SET 'latin1', ENGINE=InnoDB;
insert into test20 set pkeys='d1', val1='varchar', val2='binary', val3='blob', flags=0, cas_column=0, expire_time_column=0;
insert into innodb_memcache.containers set name='test20', db_schema='furu_test', db_table='test20', key_columns='pkeys', value_columns='val1,val2,val3', flags='flags', cas_column='cas_column', expire_time_column='expire_time_column', unique_idx_name_on_key='PRIMARY';

まぁこれは乗るねぇ。

…あんまり長い文字列だとどうなるんだろう? という疑問があるにはしても。


create table test21 (
  pkeys varchar(256) not null,
  val1 blob,
  val2 blob,
  val3 blob,
  flags int,
  cas_column bigint UNSIGNED,
  expire_time_column int,
  PRIMARY KEY(pkeys)
) CHARACTER SET 'latin1', ENGINE=InnoDB;
insert into test21 set pkeys='d1', val1='varchar', val2='binary', val3='blob', flags=0, cas_column=0, expire_time_column=0;
insert into innodb_memcache.containers set name='test21', db_schema='furu_test', db_table='test21', key_columns='pkeys', value_columns='val1,val2,val3', flags='flags', cas_column='cas_column', expire_time_column='expire_time_column', unique_idx_name_on_key='PRIMARY';

…あ、乗った。varbinaryは駄目なのに、blobはいいんだ(苦笑


create table test22 (
  pkeys varchar(256) not null,
  val1 varbinary(128),
  val2 blob,
  val3 blob,
  flags int,
  cas_column bigint UNSIGNED,
  expire_time_column int,
  PRIMARY KEY(pkeys)
) CHARACTER SET 'latin1', ENGINE=InnoDB;
insert into test22 set pkeys='d1', val1='varchar', val2='binary', val3='blob', flags=0, cas_column=0, expire_time_column=0;
insert into innodb_memcache.containers set name='test22', db_schema='furu_test', db_table='test22', key_columns='pkeys', value_columns='val1,val2,val3', flags='flags', cas_column='cas_column', expire_time_column='expire_time_column', unique_idx_name_on_key='PRIMARY';

………blogが乗ったから…と思ったけど、やっぱり駄目なんだよねぇvarbinaryは orz


create table test23 (
  pkeys varchar(256) not null,
  val1 date,
  val2 datetime,
  val3 timestamp,
  flags int,
  cas_column bigint UNSIGNED,
  expire_time_column int,
  PRIMARY KEY(pkeys)
) CHARACTER SET 'latin1', ENGINE=InnoDB;
insert into test23 set pkeys='d1', val1='1970-11-25', val2='2014-1-1 11:22:33', val3='2034-1-1 22:33:44', flags=0, cas_column=0, expire_time_column=0;
insert into innodb_memcache.containers set name='test23', db_schema='furu_test', db_table='test23', key_columns='pkeys', value_columns='val1,val2,val3', flags='flags', cas_column='cas_column', expire_time_column='expire_time_column', unique_idx_name_on_key='PRIMARY';
insert into innodb_memcache.containers set name='test23_1', db_schema='furu_test', db_table='test23', key_columns='pkeys', value_columns='val2', flags='flags', cas_column='cas_column', expire_time_column='expire_time_column', unique_idx_name_on_key='PRIMARY';

「name='test23'」はfalse。

「name='test23_1'」は一応戻ってくるんだけど、値がガッツリと文字化けしてる。まぁ「使うな」って事だわなきっと。


create table test24 (
  pkeys varchar(256) not null,
  val1 blob,
  val2 varchar(128),
  val3 int,
  flags int,
  cas_column bigint UNSIGNED,
  expire_time_column int,
  PRIMARY KEY(pkeys)
) CHARACTER SET 'latin1', ENGINE=InnoDB;
insert into test24 set pkeys='d1', val1='aaa', val2='bbb', val3=999, flags=0, cas_column=0, expire_time_column=0;
insert into innodb_memcache.containers set name='test24', db_schema='furu_test', db_table='test24', key_columns='pkeys', value_columns='val1,val2,val3', flags='flags', cas_column='cas_column', expire_time_column='expire_time_column', unique_idx_name_on_key='PRIMARY';

混ぜると…「string(11) "aaa|bbb|999"」あ、平気だった。


以上、ものっそ長い備忘録w

*1:innodb_memcache.config_options が怪しいが、簡単にテストした程度だと、変更は出来なかった

2014-03-07

[]あぁ2038年問題か

MySQLにデータを入れようとしたら


Incorrect datetime value


で怒られた。

…なんでだろ? って思ったら、timestamp型で、'2054-1-1 22:33:44'って値を入れようとしてた。

timestampって「TIMESTAMP データ型の範囲は、'1970-01-01 00:00:01' UTC から '2038-01-19 03:14:07' UTC です」だから、うん、確かにアウトだ(笑


ちっちゃい話なんだけど、意外と忘れそうなので、memo。