日向夏特殊応援部隊

俺様向けメモ

SQL::String

良し悪しはまた別に置いといて。

#!/usr/bin/perl

use strict;
use warnings;

use Data::Dump qw(dump);
use Perl6::Say;
use SQL::String;

sub ss {
    my ($sql, @params) = @_;
    SQL::String->new($sql, @params);
}

my ($country_min_population, $city_min_population)
    = (50000000, 1000000);

my $sub_query = ss(q|SELECT Code FROM Country WHERE Population > ?|, $country_min_population);
my $query     = q|SELECT ID, Name, CountryCode, District, Population FROM City WHERE CountryCode IN (| . $sub_query . ss(q|) AND Population > ?|, $city_min_population);

say dump(+{
    sql    => $query->sql,
    params => $query->params_ref,
});

__END__

SELECT ID, Name, CountryCode, District, Population FROM City WHERE CountryCode IN (SELECT Code FROM Country WHERE Population > 50000000) AND Population > 1000000;

こんなのを実行すると、

{
  params => [50000000, 1000000],
  sql => "SELECT ID, Name, CountryCode, District, Population FROM City WHERE CountryCode IN (SELECT Code FROM Country WHERE Population > ?) AND Population > ?",
}

と言う感じ。意図通りに出る。

SQL::String objects ALWAYS evaluate as true, stringify to just the SQL, and act properly in concatination, merging in other parameters in the correct order as expected.

つまるところ、文字列のように連結した場合、bind する為のパラメータも順序を保持してくっつけてくれるよーって事。

ただ、文字列のように連結しなければその特性は活かす事が出来ない。具体的にはそのまま join とかすると bind のためのパラメータがすっ飛びます。

なので、こんな感じになるのかな。

#!/usr/bin/perl

use strict;
use warnings;

use Data::Dump qw(dump);
use Perl6::Say;
use SQL::String;

sub ss { SQL::String->new(@_); }

sub sql_list {
    my @sqls = @_;
    ss(
        join(', ' => @sqls),
        map { UNIVERSAL::isa($_, 'SQL::String') ? $_->params : $_ } @sqls
    );
}

sub args { 
    my @items = @_;
    ss(
        substr('?, ' x @items, 0, -2), 
        map { UNIVERSAL::isa($_, 'SQL::String') ? $_->params : $_ } @items
    );
}

sub args_list {
    '(' . args(@_) . ')';
}

sub bulk_insert {
    my ($table, $columns, @values) = @_;
    my $sql = ss('INSERT INTO ' . $table . '(' . join(', ', @$columns) . ') VALUES ');
    $sql .= sql_list(map { args_list(@$_) } @values);
    $sql;
}

my $sql = bulk_insert(
    'foo',
    [qw/a b c/],
    (
        [ 0 .. 2 ],
        [ 3 .. 5 ],
        [ 5 .. 7 ],
    ),
);

say dump(+{ 
    sql    => $sql->sql,
    params => $sql->params_ref,
});

__END__

結果はこんな感じ、

{
  params => [0, 1, 2, 3, 4, 5, 5, 6, 7],
  sql => "INSERT INTO foo(a, b, c) VALUES (?, ?, ?), (?, ?, ?), (?, ?, ?)",
}

工夫次第で結構使えるんじゃね?って感想。

不満な点

  • 結構カスタマイズしないと使い勝手が悪い
    • 特に素で join 出来ない辺りは致命傷な気がする
  • placeholder 以外の部分のテンプレート化みたいな機能が欲しい
    • SQL::Abstract で言う所の ScalarRef でそのまま出力みたいな