2012-01-06
今日のワンライナー
ヘッダ付き CSV の2列目の各行の数値の和を求める、ってのがさらっと書けなかったので社内で聞いたみた。
$ tail -n '+1' mojage.csv | cut -d "," -f 2 | awk '{ s+=$1 } END { print s }'
とか。面白かったのは、
$ paste -d '+' -s ./mojage.csv | bc
みたいな奴。正確には一時ファイルが必要だけど、bc に食わすってのがちょっとお洒落さんだなーと。
2011-12-10
MyTAP + my_prove + MySQL::Sandbox で快適 SQL Unit Testing
MyTAP という MySQL 上で Test Anything Protocol を実現してしまうというプロダクトがあります。これと TAP::Parser::SourceHandler::MyTAP に付属している my_prove コマンドを使うとかなり簡単に SQL プログラミングでテストを行う事が出来ます。
また MySQL::Sandbox はだいぶ過去のエントリ*1 *2で紹介した事がありますが、異なる version の mysqld を色んな構成で簡単に作れてしまう優れものです。
今回はこれらを組み合わせてテストする方法について紹介しちゃいます。
MyTAP のインストール
MySQL::Sandbox を使って作った sandbox が $HOME/sandboxes/rsandbox_5_1_58 にあるとします。
$ cd ~/sandboxes/rsandbox_5_1_58 $ ./start_all $ wget https://raw.github.com/theory/mytap/master/mytap.sql $ ./master/use -e "SET GLOBAL log_bin_trust_function_creators = ON; SOURCE mytap.sql;"
として mysqld を立ち上げて、さらに mytap.sql をインストールしておきます。
テスト実践編
次にこんなソースを用意しておきます。
DELIMITER ; DROP TABLE IF EXISTS foo; CREATE TABLE foo ( id int(10) unsigned not null auto_increment, name varchar(32) not null, primary key(id) ) ENGINE=InnoDB; DROP TABLE IF EXISTS bar; CREATE TABLE bar ( id int(10) unsigned not null auto_increment, name varchar(32) not null, primary key(id) ) ENGINE=InnoDB; DROP TABLE IF EXISTS baz; CREATE TABLE baz ( id int(10) unsigned not null auto_increment, name varchar(32) not null, primary key(id) ) ENGINE=InnoDB; DROP TABLE IF EXISTS table_rows_stat; CREATE TABLE table_rows_stat ( id int(10) unsigned not null auto_increment, table_name varchar(128) not null, rows bigint(20) unsigned not null, published_on int(10) unsigned not null, primary key(id), key on_table_name_and_published_on (table_name, published_on) ) ENGINE=InnoDB; DROP PROCEDURE IF EXISTS record_table_rows_stat; DELIMITER // CREATE PROCEDURE record_table_rows_stat(match_table_pattern VARCHAR(255)) BEGIN DECLARE done INT DEFAULT 0; DECLARE a_table VARCHAR(255); DECLARE matches_table_cursor CURSOR FOR SELECT TABLE_NAME FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME REGEXP @match_table_pattern; DECLARE CONTINUE HANDLER FOR SQLSTATE '02000' SET done = 1; SET @match_table_pattern = match_table_pattern; OPEN matches_table_cursor; REPEAT FETCH matches_table_cursor INTO a_table; IF done = 0 THEN SET @rows_count_query = CONCAT('SELECT COUNT(*) INTO @rows_count FROM ', a_table); PREPARE rows_count_stmt FROM @rows_count_query; EXECUTE rows_count_stmt; /* SELECT @a_table, @rows_count; */ INSERT INTO table_rows_stat(table_name, rows, published_on) VALUES(a_table, @rows_count, UNIX_TIMESTAMP()); DEALLOCATE PREPARE rows_count_stmt; END IF; UNTIL done END REPEAT; CLOSE matches_table_cursor; END; // DELIMITER ;
正規表現でマッチしたテーブルを COUNT して table_rows_stat テーブルに突っ込むつもりの PROCEDURE です。実用性はまったく考えてませんw
この PROCEDURE がちゃんと動くか てすとしてみましょう。
テストはこんな感じです。
SELECT tap.plan(6); /* setup schema */ SOURCE schema/example.sql; /* call procedure and insert rows */ SET TIMESTAMP = UNIX_TIMESTAMP('2011-12-10 12:00:00'); CALL record_table_rows_stat('(foo|bar|baz)'); SET TIMESTAMP = UNIX_TIMESTAMP('2011-12-10 12:05:00'); INSERT INTO foo(name) VALUES('a'), ('b'), ('c'); INSERT INTO bar(name) VALUES('a'), ('b'), ('c'), ('d'), ('e'); INSERT INTO baz(name) VALUES('a'), ('b'), ('c'), ('d'); SET TIMESTAMP = UNIX_TIMESTAMP('2011-12-10 12:10:00'); CALL record_table_rows_stat('(foo|bar|baz)'); /* testing */ SET TIMESTAMP = 0; SELECT tap.diag('At first, all table rows are empty'); SELECT tap.eq(rows, 0, CONCAT('The rows of ', table_name, ' at ', FROM_UNIXTIME(published_on), ' equals ', 0)) FROM table_rows_stat WHERE published_on = UNIX_TIMESTAMP('2011-12-10 12:00:00') ORDER BY id ASC; SELECT tap.diag('Second, all table has few rows'); SELECT tap.eq(rows, 3, CONCAT('The rows of ', table_name, ' at ', FROM_UNIXTIME(published_on), ' equals ', 3)) FROM table_rows_stat WHERE table_name = 'foo' AND published_on = UNIX_TIMESTAMP('2011-12-10 12:10:00') ; SELECT tap.eq(rows, 5, CONCAT('The rows of ', table_name, ' at ', FROM_UNIXTIME(published_on), ' equals ', 5)) FROM table_rows_stat WHERE table_name = 'bar' AND published_on = UNIX_TIMESTAMP('2011-12-10 12:10:00') ; SELECT tap.eq(rows, 4, CONCAT('The rows of ', table_name, ' at ', FROM_UNIXTIME(published_on), ' equals ', 4)) FROM table_rows_stat WHERE table_name = 'baz' AND published_on = UNIX_TIMESTAMP('2011-12-10 12:10:00') ; CALL tap.finish();
これらをこんな感じで配置します。
$ tree .
.
├── schema
│   └── example.sql
└── t
└── test_record_rows_stat.my
この状態で、
$ my_prove --verbose --database example --mysql-bin ~/sandboxes/rsandbox_5_1_58/master/use t/ t/test_record_rows_stat.my .. 1..6 # At first, all table rows are empty ok 1 - The rows of bar at 2011-12-10 12:00:00 equals 0 ok 2 - The rows of baz at 2011-12-10 12:00:00 equals 0 ok 3 - The rows of foo at 2011-12-10 12:00:00 equals 0 # Second, all table has few rows ok 4 - The rows of foo at 2011-12-10 12:10:00 equals 3 ok 5 - The rows of bar at 2011-12-10 12:10:00 equals 5 ok 6 - The rows of baz at 2011-12-10 12:10:00 equals 4 ok All tests successful. Files=1, Tests=6, 0 wallclock secs ( 0.02 usr + 0.01 sys = 0.03 CPU) Result: PASS
みたいにやりますよ。
ちょっと BK ですが、--mysql-bin オプションで作った sandbox の master に付属している mysql コマンドを指定しちゃった方が幸せです。
まとめ
これで好きなビルド方法で好きなバージョンの好きな構成にて、SQLだけのテストが書けますよと。
例えば mysql のバージョン移行時に何かしたりだとか、あるいは監視系にも使えるかもしれないし、セットアップ時の項目のテストにも出来そうですね。SHOW VARIABLES とか使ってですけど。
何かと夢が広がりんぐですねー。
2011-11-25
replication status plugin のインストール
Replication Status Plugin という物がありまして、SHOW SLAVE STATUS で取れる値が information_schema.SLAVE_STATUS テーブルから取れるよって代物です。
SHOW 系のコマンドって変数代入出来ないから SQL でホゲホゲする事が出来ないんですよね。なのでこういう物があると中々便利かなと。
how do I install the Mysql Replication Status plugin に書いてあるまんまですが、こんな感じでインストールします。BUILDDIR が MySQL を一回ビルドしたディレクトリ、INSTALLDIR が MySQL をインストールした(--prefixで指定した値)ディレクトリだとして、
$ wget http://launchpad.net/is-replication-status/trunk/0.1/+download/MySQL-is_replication_status-0.1.tar.gz $ tar xfz MySQL-is_replication_status-0.1.tar.gz $ cd MySQL-is_replication_status-0.1 $ ./configure --with-mysql-src=$BUILDDIR --prefix=$INSTALLDIR $ make $ make install
みたいな感じにしておくと、$INSTALLDIR/lib 以下に is_replication_status.a is_replication_status.la, is_replication_status.so ファイルが出来上がります。
このままだと install 出来ないので、
$ cd $INSTALLDIR/lib/ $ mv -v is_replication_status.* mysql/plugin
としてあげてから、おもむろに MySQL のコンソールより、
INSTALL PLUGIN MASTER STATUS soname 'is_replication_status.so'; INSTALL PLUGIN SLAVE_STATUS soname 'is_replication_status.so';
とかしてやるとインストール完了です。
利用方法
もの凄い簡単です。SHOW SLAVE STATUS で取得出来るフィールドがそのままカラム名になっていて、テーブルには1レコードしか入ってませんので、例えば
USE information_schema; SELECT Exec_Master_Log_Pos FROM SLAVE_STATUS;
みたいな感じで使います。
2011-10-29
Pod ドキュメントを除外してソースのみを見る
それ perldoc -M と Pod::Strip で出来ます。
例えば、
$ perldoc -MPod::Strip AnyEvent
みたいな感じですね。AnyEvent の pod 部が多過ぎてソース読むのが大変だったので、これで読みやすくなりますね。
ひょっとしたらもっと良い、あるいはシンプルな方法があるやもしれないので知ってる人が居たら是非教えて下さいまし。
2011-10-27
Lion での EV(libev) のインストール
どうも Lion 環境下だとそのまま EV-4.03 はインストール出来ないみたいですね。
- Lion で Plack をインストールすると EV のテストでエラーがでる。
- Bug 9891 – static const struct optimized-away incorrectly by llvm-gcc
- Trouble installing EV-4.03 on Mac OSX 10.7 (Lion)
llvm-gcc のバグが原因だそうで、
$ perl Makefile.PL CC=gcc
としてインストール出来ましたとさ!


