dorivenの日記

気がついたら社会人。気になる技術的なことについて少しずつ書いていけたらと思っております。

頑張って作っていたものがミドルウェアのオプションひとつで実現できた話

どうも、お久しぶりです。
月日は経ち色々と状況は変わり、ちょっとこちらのブログ放置していましたが久々に記述していきたく。

tl;dr

MySQLPHPから使用しているときに、宣言した型の上限などを超えるような値が来たときのために
バリデーションを掛けてエラーを出すというのがダルいから、
それ用のライブラリを作っていたらMySQLSQLモードというオプションで実現できたという話。

背景

Webでは外部から入力されたパラメータはそのデータが妥当かどうか、
というチェック・バリデーションを行うのだけど、
ここのバリデーションで自分は大枠で2つのことを行っていた。

  • データベースの型(ここではMySQLを想定)として許容するあたいか
    • (e.g. HTMLフォームでは250文字という指定をして、型もVARCHAR(250)にしている場合に250文字以内かチェックする)

で、以前から私はこの後者のデータチェックが面倒だと思っていた。
なぜならRDBMS側でこのことをエラーとして伝えくれれば良いと考えていたからだ。
しかし、実際に挿入した値が上限を超えている場合にMySQLは以下のような挙動をする。*1

  • 数値型の上限を超えていたなら、上限に一致させる
    • (e.g. TINYINT型(上限:127)に対して200の値を挿入しても127に丸められる)
  • 文字列型の文字数上限を超えていたなら、文字を切り詰める
    • (e.g. VARCHAR(5)型に'1234567890'の文字列を挿入しても'12345'まで切り詰められる)

こういうことをやってくれるので、意図しないデータが入らないようにするためにもバリデーションを行っていた。
型に違反した値が挿入されようとしたらSQL側でエラーにしてくれよ、と個人的には感じていた。

ライブラリを作ろうと思った

ある時、ある箇所で後者のバリデーションをしていないがために障害に対する検知が遅れたことがあった。
しかしながら、一つ一つデータの挿入箇所に対して後者のバリデーションを敷くのは大変なので、
入力値とテーブル定義の情報から自動的にバリデーションをしてくれるライブラリがあれば良いと思った。

が、ぐぐってもそんなライブラリは出てこなかった*2ので自分で作ろうと一念発起した。

機能としては以下のようなものを想定していた。

  • 型の入力値の許容範囲内かのチェック
  • Nullableではないものに対してNullチェック
  • 存在しないカラムのデータがあるかのチェック

初めてpackagistに登録を行い、あんまり使わない個人のアカウントにこのライブラリのリポジトリを登録したり、
composer.jsonの記述を行ったりして結構初めて尽くしで面白みはあったし、
将来このライブラリが他人の役に立つならと思うと結構ワクワクしていた。

無駄なことをやっている事に気づく

全ての型はサポートしていないもののある程度のバリデーションが掛けられるものが出来上がり、
あとは未実装な型のバリデーションを徐々にしていくだけだなと思い調査を進めていた。

TIME型の実装をするためにMySQLのリファレンスを参照しているときのことである。
MySQL :: MySQL 5.6 リファレンスマニュアル :: 11.3.2 TIME 型

無効な TIME 値の制限を厳しくするには、エラーが発生するように厳密な SQL モードを有効にしてください。セクション5.1.7「サーバー SQL モード」を参照してください。

今の課題をいい感じにしてくれそうな雰囲気の記述とリンクががが。

TRADITIONAL

MySQL を 「従来型の」 SQL データベースシステムのように動作させます。このモードを簡単に説明すると、カラムに不正な値を挿入したときに「警告ではなくエラーを返し」ます。これは、このセクションの末尾にリストされている、特殊な組み合わせモードの 1 つです。

なん…だと…

実装を進めながら薄々「これ、もうRDBMS側でよしなにやってくれたほうがいいよなー。
そもそもWarning出してるんだからそこらへんのチェックしているはずなのに」と思っていたのが、そんなのがあったとは。

更にこのSQLモードなるものを追っていると次のような記事にあたった。

sakaik.hateblo.jp

先人がめっちゃいい感じで解説してくれている。

結論

僕が頑張って実装していた機能は以下のオプションをmy.cnfや起動オプションに付けることで実現できることがわかった。

sql_mode='TRADITIONAL,PIPES_AS_CONCAT'

学び

  • ライブラリがありそうなのにない場合は代替手段がある可能性が高い

この一点だけである。

ソフトウェアエンジニアあるあるなのかもしれないが、代替手段を知らずに頑張っていたら
代替手段で1/100のコストで実現できたという話の典型パターンだった。

無知は罪なり、とはまさしくこのことで、自信のMySQLに対する理解力のなさが招いた話だった。

一方でライブラリを作ることで以下のような副次的な利点もあった。

型に詳しくなった

バリデーションを行うために型に関しての情報を集める必要があったので型について詳しくなった。
日頃使わないような型や、型の細かいオプションなどの存在を知れて面白かった。

AbstractFactoryパターンについての理解が深まった

頭では理解していたが、実際にパターンを実装したことがなかったので今回のでAbstractFactoryへの理解が深まった。
活躍所として、RDBMSの要素が決定したときに生成される必要な部品の生成に大いに役立ってくれた。

趣味でライブラリを作るときにはTDDが便利

とりあえず、趣味で書いていると取れる時間もバラバラだし何をどこまでやっていたか忘れるし、
自分がどんなコードを書いたのかもちょっとうろ覚えになっていく。
そんなときにとりあえずテストを先に書いて通る・通らないかを確認することで実装の進み具合などをテストで可視化できてよかった。


ちょっと痛い目を見たけど、色々といい経験になったと思う。

そして、データベースのデータは資産であり、クソみたいなデータが入らないために
データはがっちり守ったほうがいいと色々と経験して痛感した日々だった。

*1:もちろんwarningは出てくれるのだが

*2:今思うとこの時点でそのような需要がない、 つまり別の解決策があるということに気づくべきだった