SQL::Abstract で LIKE の ESCAPE を指定する

正規表現でいう ^a_c.* みたいなのをひっかけようと思って,

my ($sql, @binds)
    = SQL::Abstract->new()->select(
        # table
        'foo',

        # fields
        [ '*' ],

        # where
        {
            id => { LIKE => 'a_c%' },
        },
    );

みたくやると,a_code だけでなく abc もマッチしてしまう罠。

PostgreSQL だと,デフォルトでエスケープ文字が \ になってるので,

        # where
        {
            id => { LIKE => 'a\\_c%' },
        },

のようにエスケープすればいい(MySQL だとどうなんだろ)。

でも,たとえば SQLite はデフォルトでエスケープ文字が設定されてない。だから LIKE などのあとに ESCAPE を指定するのが本筋。なんだけど,SQL::Abstract でふつーにやろうと思うとうまくいかない。


最初全部をリテラル SQL で指定するか,SQL::Abstract を改造(拡張)する((sub _where_hashpair_HASHREF() に手を加えればいけるかな,というとこまで調べた。))しかないかなぁと思ったんだけど,調べたらちゃんと逃げ道があった。それがプレースホルダつきリテラル SQLLiteral SQL with placeholders and bind values (subqueries) - SQL::Abstract)。

リテラル SQL とは,昔言及した*1

        # where
        {
            id => \"IS NULL",
        },

みたいなやつのこと。

プレースホルダつきバージョンは,今回の場合,以下のように指定する。

        # where
        {
            id => { LIKE => \[ '? ESCAPE ?',  'a\\_c%', '\\' ] },
        },

もちろん

            id => \[ 'LIKE ? ESCAPE ?',  'a\\_c%', '\\' ],

こう書いてもいい。

ようするに,配列リファレンスの「さらにリファレンス」を渡すと,第二引数以下をバインド値として,プレースホルダつき SQL が発行されるわけです。


ここまでして SQL::Abstract に固執する必要があるかは人によりけりとは思いますが,メモメモ。