views:

242

answers:

2

Current code base I'm working on is full of ad-hoc conditional string concatenations producing less than clear SQL queries. I want to make them maintainable, but since using DBIx::Class is too complex to move to for now (giant legacy base), I'm looking to at least make them more robust by using some sort of SQL generator, which would only create the SQL by object-orientation or any other clean technique (no DB handling necessary).

One general constraint on that generator is the ability to use stored procedures in a sane way, since my application is mostly based on those. For example, I need to SELECT * FROM StoredProcedure(Parameter) WHERE .... I've looked into Fey::SQL, SQL::Abstract and some others, but haven't seen any support apart from "inline SQL" for this kind of statement. Neither have I seen any support for EXECUTE ..., not even in DBIx::Class, which I frankly can't really believe, probably I've been looking in wrong places.

I actually liked Fey::SQL's approach, until I found out it required some sort of scheme:

 $select->select( $user->columns( 'user_id', 'username' ) )
     ->from( $user, $group )
     ->where( $group->group_id, 'IN', 1, 2, 3 )
     ->and  ( $func, 'LIKE', 'smith%' );

What would you recommend?

+3  A: 

You can try SQL::Abstract. It's nice for simple SQL statements

Ivan Nevostruev
Yes, except I explicitly mentioned stored procedures which are not possible with `SQL::Abstract`, as far as I can see.
rassie
Well, in your "giant legacy base" case, I think you should create your own solution. It will be much easier to use in existing code. And it can be based on some modules you've mentioned.
Ivan Nevostruev
+1  A: 

I think the main problem is I do not understand what function(?) does. Is the ? there a placeholder to be passed to a stored procedure called function? I also do not have access to a database where I can test my understanding of this. However, does the following really not work?

my $sql = SQL::Abstract->new;

my ($st, @values) = $sql->select(
    \'function(?)',
    '*',
    { group_id => { 'IN' => [ 1 .. 3] } },
);

my $sth = $dbh->prepare($st);
$sth->execute('arg1', @values);

In my case, $st contains:

SELECT * FROM function(?) WHERE ( group_id IN ( ?, ?, ? ) )

If that does not work, then how about:

my ($st, @values) = build_select_for_function(
    function => [ qw(arg1) ],
    '*',
    { group_id => { 'IN' => [ 1 .. 3] } },
);

print $st, "\n";


sub build_select_for_function {
    my ($func, $args, @sql_abstract_args) = @_;
    my $func_str = sprintf '%s(%s)', $func, join(',', @$args);

    my $sql = SQL::Abstract->new;
    $sql->select(
        \$func_str,
        @sql_abstract_args,
    );
}

Output:

SELECT * FROM function(arg1) WHERE ( group_id IN ( ?, ?, ? ) )
Sinan Ünür
There are already a lot of heredocs, which I dislike for formatting and highlighting reasons. The main reason I need a generator is being able to overwrite or extend the WHERE-clause or change field set depending on certain conditionals. This is overly complicated with strings, but would be rather easy if I could just "set" parts of the query and then generate it in one go.
rassie
@Sinar: Even rather simple stuff like server-side pagination (T-SQL) can get out of hand rather fast :(
rassie
I've tried it now, still playing, but in general it's not a particularly good way for the job. Besides finding the syntax a bit awkward (could be better I guess), I couldn't find a way to bind a value to a table/function name (`select * from function(?)`) and also no way to make a `select distinct column from`. However, doing a `\qq{select * from function($bla)}` works fine, so it could have some use for me nevertheless.
rassie
@Sinan: sadly, SO is rather unsuited for discussion, but I'll try my best: The DB I have to use for my application uses SPs extensively, i.e. I don't have access to raw tables. Every function requires a token parameter, which ultimately authorizes currently logged in user to do some action. So my app does this: login -> receive token -> call functions. Your first example won't work, since that token is not a bound parameter in `@values` and the second example is essentially string interpolation, which works and is usable, but is not as clean as it might be with proper support for functions.
rassie