SQL::Abstract::Fulltext::MySQLを書いてみた。

SQL::Abstract::Fulltext::MySQLを書いてみた。

 ちゃんと動作確認はしてないけど。

package SQL::Abstract::Fulltext::MySQL;
use strict;
use warnings;
use Carp;

my $org_recurse_where = main->can('SQL::Abstract::_recurse_where');
sub import {
    croak "must be require SQL::Abstract" unless $org_recurse_where;
    no warnings 'redefine';
    *SQL::Abstract::_recurse_where = \&_recurse_where
        if $org_recurse_where != \&_recurse_where;
}

sub _recurse_where {
    my $self = shift;
    return $org_recurse_where->($self, @_) unless uc($_[1]) eq 'FULLTEXT';
    _make_fulltext_where($self, @_);
}

sub _make_fulltext_where {
    my $self = shift;
    my $where = SQL::Abstract::_anoncopy($_[0]);   # prevent destroying original
    my $ref   = ref $where || '';

    my $bind_col;
    my $match;
    my $against;
    my $boolean;
    while (my($k, $v) = each %$where) {
      my $k = uc $k;
      if ($k eq '-MATCH') {
        if (ref $v eq 'ARRAY') {
          $bind_col = $v->[0];
          $match = join ', ', map +$self->_quote($_), @$v;
        }
        else {
          $bind_col = $v;
          $match = $self->_quote($v);
        }
      }
      elsif ($k eq '-AGAINST') {
        $against = $v;
      }
      elsif ($k =~ /-BOOLEAN(_MODE)?/) {
        $boolean = $v;
      }
    }

    my $where_sql = sprintf "MATCH ( %s ) AGAINST ( ? %s)",
    	$match,
    	($boolean? 'IN BOOLEAN MODE': '')
    	;

    return $where_sql, $self->_bindtype($bind_col, $against);
}

1;
__END__

 使い方としては、

package CDBI::Foo;
use base qw/Class::DBI/;
use Class::DBI::AbstractSearch;
use SQL::Abstract::Fulltext::MySQL;

# 以下略
package main;

# WHERE MATCH ( description ) AGAINST ('+foo +bar' IN BOOLEAN MODE)
my @rows = CDBI::Foo->search_where({
  -fulltext => {
    -match   => 'description',
    -against => '+foo +bar',
    -boolean => 1,
  },
});

――みたいな。Class::DBIの場合はClass::DBI::AbstractSearchを使うかClass::DBI::Sweetを*1使っているときに利用できます。

 DBIx:ClassはSQL::Abstractを使っているので(確か)、useすれば-fulltextが使えるようになります(未確認動くのを確認しました)。

 どこかでuse SQL::Abstract::Fulltext::MySQLしちゃうとSQL::Abstractを書き換えちゃうのでアレだけど。特定クラスだけ変更するのは面倒だから気にしてない。

 本当はSQL::Abstract::Limitみたいに各種RDBMS互換にしたいけど、MySQL以外は知らないもので。。。

 対応しやすくしてる(つもりな)ので、そのうちなんとかしたいなぁ*2

*1CDBI:Sweetのsearchだとうまく動かない。あとで調べる……。調べた。_bindcol()をちゃんと使わなかったのがまずいっぽい。_convert()とか_sqlcase()も使うべきのような気がするけど。

*2:その場合は-fulltextに渡すパラメータ(HASH_REF)をどうするのか考えないといけないけど。