2012年9月4日火曜日

MySQLでvarcharカラムをintと比較するとおかしな結果になる

9月7日に真相(?)について追記しました。

自明のことだがSQLではWHERE句で指定した条件にマッチしたレコードのみを取得することが出来る。だが、MySQLでは文字列型のカラムに対して = 数値 という条件にすると、おもしろい(おかしな)結果になる。

対象テーブルの定義とデータ


以下のようにとてもシンプルなテーブルがある。

CREATE TABLE `string_table` (
`id` int(11) NOT NULL,
`value_string` varchar(1024) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

データはそれなりにたくさん入っている。

select count(*) from string_table;
→ 296,458行

クエリとその結果


まずは普通に文字列と完全一致で検索。

select * from string_table where value_string = 'aaa';


これは自明。
では次に0と完全一致で検索。予想では型が異なるのでSQLエラーになると思いきや、

select * from string_table where value_string = 0;

と、結果が取得出来る。それも件数を確認すると296,442行となっており、ほぼ全てのレコードが検索結果として出ていることが分かる。???なんだこれは。

ということで似たような条件で検索してみると…

select * from string_table where value_string = 1;


SQLはエラーにならず、結果は0件。

select * from string_table where value_string = 5;


SQLは当然エラーにならず、結果は3件。

select * from string_table where value_string like '5%';

で検索してみた結果と同一だったので、先頭が5の文字列のレコードがヒットしている。

結論


MySQLでは文字列型のカラムに対して条件を integer で指定すると、思わぬレコードが取得できてしまう。

なんでこうなるのかが気になるので色々と調べてみたが理由が分かっていない…。


真相


ブログを見てくれた方から情報をいただいた。


http://blog.usoinfo.info/article/180598548.html
解説はほぼ上記のブログで完結しているが、少しだけ補足する。

MySQLでは、左辺と右辺の型が異なる場合、どちらかに合わせて(変換して)比較が行われる。

文字列と文字列であれば変換は発生しないので意図した通りの結果になるが、今回は左辺が文字列で右辺が数値になっている。この場合、MySQLでは左辺と右辺の両方を数値に変換してから比較を行う。今回のケースでは左辺には既存のレコードの値が入ってくるが、数字になり得ないもの( aaaaa, bbbbb, aa033e など)が来た場合は全て0に変換されてしまう。

今回の検索結果の1行目を例にすると、

select * from string_table where value_string = 0;

↓ (1行目のデータが入ったとすると)

select * from string_table where 'sc167' = 0;

↓ (sc167は数字になり得ないので0に変換される)

select * from string_table where 0 = 0;

この時、 where句は true となるためこのレコードが抽出結果として出てくる。

なお、=5 の時は先頭が5となっている文字列が全て抽出されている。
どうやらMySQLは先頭が数字で始まっている文字列は数字が終わるまでの部分で数値に変換してくれるようだ。

とてもすっきりした。

情報をくださった方、ありがとうございました。

補足


そもそも、こんなことが起きたのは Prepared Statement を使用して php から MySQL のデータを取得していたのだが、埋め込むパラメータを変数で渡していたのだが、明示的に文字列にキャスト等をしておらず、文字列が全て数字で構成されていた場合に文字列ではなく数値と扱われてしまい、最終的に生成されたSQLが = 0 というようになってしまって今回の現象が起きた。

ちなみに Oracle で = 0 を試したところ結果は0件だった。SQLの仕様ではなさそうだが真相はよく分かっていない。
ソースを読めば分かるけどね…。

2012年8月15日水曜日

vert.x を使ってみた

vert.x なるものが軽量でとても良いと聞いたので、使ってみることにした。

vert.x とは


アプリケーションフレームワーク(サーバー)である。

公式である http://vertx.io/ を見て特徴をかいつまむとこんな感じ。

  • Polyglot
    複数の言語が使えるということ。Javascript, Coffee Script, Ruby, Python, Groovy, Javaなど。
    なおかつ、どれか1つではなくミックスして使うことが可能
  • Simplicity
    シンプル。必要なコードだけ書けば動く。設定の XML ファイルなどはない
  • Scalability
    拡張可能。メッセージ渡し、変更されない共有データによって最適化される。
  • Concurrency
    並列処理。超シンプルな並列処理の仕組みがあり、いわゆるマルチスレッドプログラミングの難しさに悩むこともない
  • WebSocketのサポート

インストール


今回は Ubuntu にインストールしてみる。

まずは vert.x をダウンロード

wget http://vertx.io/downloads/vert.x-1.2.3.final.zip

ここ http://vertx.io/install.html を見ると、必要なコンポーネントに JDK7 が必要ということなので、

sudo apt-get install openjdk-7-jdk でインストール

vert.x のインストールはファイルを解凍してパスを通せば良いようだ。

ということで、 vi ~/.profile で以下を追加

# set vert.x path to PATH
if [ -d "/usr/local/bin/vertx" ]; then
    PATH="/usr/local/bin/vertx/vert.x-1.2.3.final/bin:$PATH"
fi

追加しただけでは反映されないので、

source ~/.profile

で、即時反映。

ということで vert.x が動くかどうか確認

vertx version
と打ったら
vert.x-1.2.3.final
と表示されたのでOK

動作確認


とりあえず javascript で動かしてみる。

適当な場所に first_server.js としてファイルを作成し、以下の内容を記載。

load('vertx.js');

vertx.createHttpServer().requestHandler(function(req) {
    req.response.end("Hello vert.x world.");
}).listen(8080, 'localhost');

その後、以下のコマンドを実行。

vertx run first_server.js

ブラウザで http://localhost:8080/ にアクセスすると意図通りに表示された。




感想的なもの



  • インストールは簡単
  • javascript のソースを書いたが、至極シンプル。
  • 今後はサポートされている他の言語も使ってみようかと思っている(Java, Ruby, Pythonなど)

2012年4月9日月曜日

rsync の include 使用方法(include と exclude の整理)

rsync コマンドを include オプションを指定して実行しようとしたがうまくいかなかったが、ネットをあれこれ検索していたらうまくいったのでその結果をまとめる。

その前に include と exclude オプションとは

rsync コマンドには --include オプションと --exclude オプションがある。それぞれの意味は以下の通り。
  • --include : --include="hogehoge" で指定した文字列にマッチするファイルまたはディレクトリが同期対象となる
  • --exclude : --exclude="hogehoge" で指定した文字列にマッチするファイルまたはディレクトリが同期対象から除外される
つまり /var の中の httpd.log のみを同期することや /var の中の httpd.log 以外を同期するといったことが可能となる。

じゃあやってみよう


ということで以下のようなディレクトリとファイルがあるとする。

同期対象のディレクトリ構成



これに対して以下のコマンドを実行する。
rsync -av --delete --include="config" rsync_test/ to_dir01
予想では rsync_test/config のみが to_dir01 に作成されるはずだ。だが、実際は…


となり、すべて同期されてしまう。
ここで、 man などでヘルプを見てみて--excludeと一緒に指定しないと駄目そうと判明したので以下のように実行してみた。デフォルト全て除外でconfigだけ対象という意図だ。
rsync -av --delete --exclude="*" --include="config" rsync_test/ to_dir02
これで実行した結果が以下のようになる。


きちんと指定しているにも関わらずすべて対象外となり何も同期されない。この時、
  • 指定方法がフルパスじゃないと駄目?
  • 対象がディレクトリだから最後に / がないと駄目?
という試行錯誤を小一時間繰り返したが解決しない…。

include と exclude オプションは順序が大事


man を見ても分からないのでGoogle先生に聞いてみた。そしたら以下のサイトが見つかった。


まさに今回の件にドンピシャ。ということで以下のように変更してみた。
rsync -av --delete --include="config" --exclude="*" rsync_test/ to_dir03
これで実行した結果がこちら。


うまくいった。結局は使い方が間違っていたということか。

にしても今までコマンドをいろいろ使ってきたけどコマンドラインオプションに順序性があることがあるということを初めて知った。なんとなく違和感があるけどそういうものなのか…


include と exclude の指定方法による実行結果への影響

ということでうまくいったのでいろんなパターンを試してみる。

1.includeで直下のファイル名を指定した場合

rsync -av --delete --include="Rakefile" --exclude="*" rsync_test/ p01

対象のファイルのみが同期され、それ以外のファイルディレクトリ、ファイルは除外された。

2.includeで直下のディレクトリ名を指定した場合

rsync -av --delete --include="app" --exclude="*" rsync_test/ p02

対象のディレクトリのみが同期され、そのディレクトリ内のファイル、ディレクトリ、およびそれ以外のファイル、ディレクトリは除外された。

3.includeで直下でないディレクトリ名を指定した場合(失敗)

rsync -av --delete --include="controllers" --exclude="*" rsync_test/ p03

controllersという名前のディレクトリは存在していますが、パスの指定方法は相対パス指定であるため、今回の指定方法の場合はrsync_test/controllersというディレクトリを指定していることになり、そのようなディレクトリはないため何も同期されないという結果になる。

4.includeで直下でないディレクトリ名を正しく指定した場合(失敗)

rsync -av --delete --include="app/controllers" --exclude="*" rsync_test/ p04

今回は正しく指定しているにも関わらず、対象のディレクトリが作成されていない。つまり、この指定方法ではうまくいかない。原因は後回しにし次のパターンへ。

5.includeで直下でないディレクトリ名と対象ディレクトリの親ディレクトリを同時に指定した場合

rsync -av --delete --include="app" --include="app/controllers" --exclude="*" rsync_test/ p05

今回はうまくいった。つまりサブディレクトリを指定したい場合は、対象のディレクトリに至るまでの全ての親ディレクトリを--includeで指定する必要があるということだ。

6.includeでディレクトリ名/*を指定した場合(失敗ケース)

rsync -av --delete --include="app" --include="app/controllers/*" --exclude="*" rsync_test/ p06

appまでは作成されたがapp/controllers以降が作成されなかった。これはapp/controllers/*はあくまで指定したディレクトリに存在するファイルやサブディレクトリを対象としているのであり、その時点でapp/controllersディレクトリが対象に含まれていないためにapp/controllersディレクトリが作成されないためファイルやサブディレクトリが作成されない。つまり、は次のように指定する必要がある。

7.includeでディレクトリ名/*を指定した場合

rsync -av --delete --include="app" --include="app/controllers" --include="app/controllers/*" --exclude="*" rsync_test/ p07

このように実行すれば目的の動作となる。これはなんとかならないものか…。


8.includeでディレクトリ名/**を指定した場合

rsync -av --delete --include="app" --include="app/controllers" --include="app/controllers/**" --exclude="*" rsync_test/ p08

指定したディレクトリに含まれるファイルとサブディレクトリ、そのサブディレクトリに含まれるファイルやサブディレクトリ全てが同期される。

まとめ

includeについてはひとまずこれで終了。まとめると以下のようになる。
  1. include で特定のディレクトリやファイルを指定したい場合は include を先に書いて --exclude="*" を後に指定しなければならない
  2. include でファイルやディレクトリを指定する場合は相対パスで指定しなければならない
    パターン3を参照
  3. include でサブディレクトリやサブディレクトリにあるファイルを指定する場合は、対象のファイルまたはディレクトリに至るまでのディレクトリをすべて--includeで指定しなければならない
    パターン5を参照
  4. include でディレクトリ以下のファイルを指定したい場合は対象のディレクトリ対象のディレクトリ内のファイル(ディレクトリ)は別々に --include で指定しなければならない
    → パターン7,8 を参照

余談

excludeはまとめる予定は今のところなし…。

2012年1月4日水曜日

JBoss4.2にMySQLに接続するためのDatasourceを作ってS2JDBCで接続する

JBoss の Datasource を経由して MySQL に接続してみた。

1.準備

JBoss は 4.2.3GA を準備。
MySQL は 5.1.58 community を準備。

インストール後に以下の手順が必要になります。

1.1. データベーススキーマの作成

MySQLに任意の名前でスキーマを作成します。既に存在していればそれを使用しても構いません。
詳細は省略します。

1.2. MySQL用のJDBCドライバーを配置

ここ( MySQL :: Download Connector/J )から対応するJDBCドライバーをダウンロードします。
その後、 mysql-connector-java-5.1.6.jar を以下のディレクトリに配置します。

{JBossのインストールディレクトリ}/server/default/lib/

2.Datasource の定義ファイルを作成

2.1. ファイルの作成

以下のようにファイルを作成する(中身は空で良い)

ディレクトリ:{JBossのインストールディレクトリ}/server/default/deploy
ファイル名:{任意の名前}-ds.xml

ちなみに自分はここでファイル名を ds-hogehoge.xml としてしまったために、
JBossが以下のように表示するだけで全く読み取ってくれなくて悩んでたのは秘密。
いやー、ホント悩んだ。

--- Incompletely deployed packages ---
org.jboss.deployment.DeploymentInfo@13376b0a { url=file:/C:/Servers/jbos-4.2.3.GA/server/default/deploy/ds-hogehoge.xml }
deployer: null
status: null
state: INIT_WAITING_DEPLOYER
watch: file:/C:/Servers/jboss-4.2.3.GA/server/default/deploy/ds-hogehoge.xml
altDD: null
lastDeployed: 1325039984621
lastModified: 1325039984621
mbeans:

ちなみにこちらのサイトの情報でファイル名の問題に気づきました。

2.2. Datasource の定義を記述

2.1.で作成したファイルに以下のように記述する。


  
    JNDI名
    jdbc:mysql://ホスト名:ポート番号/スキーマ名?useUnicode=true&
    characterEncoding=UTF-8
    
    com.mysql.jdbc.Driver
    ユーザー名
    パスワード
    org.jboss.resource.adapter.jdbc.vendor.
     MySQLExceptionSorter
    8
    8
    0

    
    
       JNDI名
    
  


3.JBoss に Datasource が登録されているか確認

この状態で JBoss を起動。以下のように表示されJNDIに登録されればとりあえず成功です。

15:21:23,170 INFO  [ConnectionFactoryBindingService] Bound ConnectionManager 'jboss.jca:service=DataSourceBinding,name=JNDI名' to JNDI name 'java:JNDI名'
15:21:23,189 INFO  [TomcatDeployer] deploy, ctxPath=/jmx-console, warUrl=.../deploy/jmx-console.war/
15:21:23,316 INFO  [Http11Protocol] Coyote HTTP/1.1を http-127.0.0.1-8080 で起動します
15:21:23,330 INFO  [AjpProtocol] Starting Coyote AJP/1.3 on ajp-127.0.0.1-8009
15:21:23,340 INFO  [Server] JBoss (MX MicroKernel) [4.2.3.GA (build: SVNTag=JBoss_4_2_3_GA date=200807181417)] Started in 7s:379ms

4.S2JDBC経由でデータベースにアクセス

登録したJNDIを使ってデータベースに接続するにはアプリケーションの jdbc.dicon を以下のように記述。


  

  
  
    
      
    
    100
  

  
        @org.seasar.extension.j2ee.JndiResourceLocator@lookup("java:データソース名")
  



自分はこれで出来たが、諸事情により実行結果の画面やログは省略させてもらいます。

今回参考にさせていただいたサイト

JBossにMySQL用のデータソースを定義する方法について参考にさせていただいた
JBoss/MySQLと接続する / http://www.masatom.in/pukiwiki/JBoss/MySQL%A4%C8%C0%DC%C2%B3%A4%B9%A4%EB/

S2JDBCでJBossのデータソースを利用する方法について参考にさせていただいた
APサーバのコネクションプールを使う : JDBCの設定 / http://s2container.seasar.org/2.4/ja/jdbc.html#GenericApplicationServer

途中でそもそもJBossって何だ?ってなった時に参考にさせていただいた(JBossってTomcatを含んでるものなのね。そりゃtomcatより起動とか遅いわけだ…
TomcatとJBossの違い - OpenGroove : http://open-groove.net/tomcat/difference-tomcat-jboss/