Ruby
初心者
RubyOnRails

RailsにてSQLでのワイルドカード文字をエスケープしてくれるsanitize_sql_likeは何をしているのか

はじめに

Rails4.21より、Stringクラスのオブジェクト内にある”%”や”_”といったLIKE句でのワイルドカード文字をエスケープしてくれるsanitize_sql_likeメソッドが使えるようになっています。

検索機能等においてLIKE句はよく使用するものであり、ワイルドカードはきちんとエスケープしないと非常に重いSQLが発行されることになりかねません

今回はソースコードを読むという行為の練習も兼ねて、このsanitize_sql_likeというメソッド内でどのような動作が行われているのかを追っていこうと思います

まずはソースコード

def sanitize_sql_like(string, escape_character = "\\")
  pattern = Regexp.union(escape_character, "%", "_")
  string.gsub(pattern) { |x| [escape_character, x].join }
end

このメソッドはstringとエスケープのための文字escape_character(デフォルトでは"\\")を引数にとります
実行内容としては2つのステップに分かれていて、

  1. エスケープ文字とワイルドカードとして認識される文字にマッチする正規表現を生成、patternに代入

  2. stringの中にある文字でpatternとマッチする文字をエスケープ文字と連結してエスケープ

この2つの行程を通して引数string をsanitize(消毒)していきます。

エスケープ文字とワイルドカードとして認識される文字にマッチする正規表現を生成、patternに代入

  pattern = Regexp.union(escape_character, "%", "_")

SQLのLIKE検索において"%", 及び”_”はワイルドカード文字として認識されてます。
前者は任意の数の文字、後者は唯一つの文字のワイルドカードとして機能します。
12.5.1 文字列比較関数

これらをエスケープするためにはまずこれらの文字を判別する必要があるわけです。

そのためにRegexp.unionというRegexpのクラスメソッドを使用します。
Regexp.unionは引数として与えた文字にそれぞれマッチする正規表現を”|”で連結し、引数のどれかにマッチする正規表現を返します。
singleton method Regexp.union

このコードではRagexp.union の引数として2つのワイルドカード及びエスケープのための文字\\(\単体では記述できないため)をとり、判別するための正規表現を生成し変数patternに入れています。

stringの中にある文字でpatternとマッチする文字をエスケープ文字と連結してエスケープ

  string.gsub(pattern) { |x| [escape_character, x].join }

gsubはStringクラスのインスタンスメソッドで、文字列の中で正規表現にマッチした部分を指定の文字列に置換します。
gsub, gsub! (String)
このメソッドを使いワイルドカード文字をエスケープされるように置換していきます。
ブロック内のxには先程生成した正規表現patternにマッチする文字が入っていき、このxに対してエスケープ文字として指定した\\を連結して置き換えることでエスケープが完了となります

わかりやすくするとすれば

  string.gsub(pattern) { |matching_character| [escape_character, matching_character].join }

という形になりますかね

まとめ

railsには様々な便利メソッドがありますが、当然それらも何かしらのコードで作られています。今回は正規表現の勉強も兼ねてメソッドのソースコードがどのような動作をしているかを追ってみました。
プログラミングスクールの講師の方からも公式のリファレンスやソースコードを参照する癖をつけようとよく言われていましたがソースコードにもソースコードがあるわけなのでこの追求に終わりはないとも感じます

エンジニアとして転職したい23歳男性(関東)にご興味あればぜひ連絡ください(懇願)

最後まで読んでいただきありがとうございます!

参考

Ruby 1.8.7 リファレンスマニュアル
MySQL 5.6 リファレンスマニュアル
Rubyリファレンス(お世話になっています)
sanitize_sql_like(API dock)