Hatena::ブログ(Diary)

Yet Another Hackadelic

2012-03-07

perldoc.jp のデザインを公募します!

こちらでもお知らせしちゃいます!perldoc.jp の運営とデザイン公募のお知らせ - JPA 運営ブログにて詳しく書きましたが、perldoc.jp の運営母体を JPA に移管する事になりましたので、デザインの公募を行います。

もしお知り合いにデザイナー等いましたら、お声がけ下さいませ。

2012-02-11

PATCH メソッド、新しい HTTP Status Code

Spec はあまりミーハーに追いかけても後で痛い目にあったりするもんですが、久しぶりに面白いなーと思ったのでちょっと取り上げてみます。

ちなみに斜め読みなので記事の正確性についてはあまり保証しませんw

PATCH Method for HTTP

RFC 5789 にある PATCH Method for HTTP ですが、RESTful API の致命的な弱点でもある PUT がリソースの完全なる置き換えなのに対して、PATCH は差分適用である所が中々面白いです。

2.1. A Simple PATCH Example のサンプルを見てみます。

PATCH /file.txt HTTP/1.1
Host: www.example.com
Content-Type: application/example
If-Match: "e0023aa4e"
Content-Length: 100

[description of changes]

Content-Type では元のリソースに対して何らかのパッチ形式を定義して、If-Match ヘッダでは該当するバージョンみたいな物を指定してるんでしょうね。

例えば SQL ならば、

UPDATE resources SET /* description of changes */ WHERE id = 'file.txt' AND version = 'e0023aa4e';

みたいなニュアンスなんでしょうね。3.1 The Accept-Patch Header で OPTIONS メソッドとして受け入れられる PATCH メソッドの本文形式を照会出来ると、まぁ良く出来てますねーって印象です。

OpenSocial の RESTful API だと、9.3 Partial Updates でなんとなーく規定されているように、

GET /appdata/@me/@self/@app

200 OK
Content-Type: application/json; charset=utf-8
ETag: "akb1"

{
 "pokes" : 3,
 "last_poke" : "2008-02-13T18:30:02Z"
}   

みたいなエントリがあったとして、

PUT /appdata/@me/@self/@app?fields=pokes
Content-Type: application/json; charset=utf-8
If-Match: "akb1"

{
  "pokes": 4
}

とかが Partial Updates ですね。そういう訳で PUT メソッドと fields パラメータにより結構強引に差分更新を表現していた所が、より明確な意味での実現が提供されてるって感じです。

Additional HTTP Status Codes

Additional HTTP Status Codes draft-nottingham-http-new-status-04 で定義されている幾つかの HTTP Status Code は少なすぎるレスポンスコードの言葉足らずな所を補う意味で良いんじゃないかなーと思ってます。

まだドラフトなんでアレですけど、今後期待したいなと。

3. 428 Precondition Required

3. 428 Precondition Required はさっきの PATCH メソッドによるリクエストの際に、If-Match によるバージョン指定をクライアントに要求とかそういう事が出来るっぽぃですね。

4. 429 Too Many Requests

429 Too Many Requests は現実的に使いたいシーンはあるだろうなーと。

今、こういうのを表現する際に、10.5.4 503 Service Unavailable を使ったりしてたんだけど、予めリクエスト数の上限についてガイドラインを提示しているのであれば、これはクライアント側の問題に帰着させるのが正しいと思うので4xxで表現するのが妥当だよなーなんて思う次第であります。

See Also

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;

みたいな感じで使います。