This is my first release of a public module so please nudge me in the right direction.

Below you will find the POD and the modules. If you could just give me some feedback on how it looks, features needed, or just generally how it makes you feel that would be appreciated.

This is different from the other similar modules in a few ways. First it lets you control the load of SQL using a simple hash, no imposed storage techniques. You store, load and save on your own leaving you free to use ini, xml, databases or anything else that pleases you. Multiple scripts can use the same or different libraries of SQL depending on how you load them. The other thing that sets it apart is that it includes a simple mechanism to do substitutions in your stored queries. In this manner 2 queries that vary only in the colums they retrieve can be stored as one and the script running it can choose which colums to retrieve.

First a quick example so you know what it does.

use strict; use warnings; use DBI; use DBIx::Library; my $dbh = DBI->connect() or die DBI->errstr; my $sql = new DBIx::Library( dbh => $dbh, quries => { offices => 'SELECT * FROM Offices', offices_x => 'SELECT $$what$$ FROM Offices' +, user => 'SELECT `username`, `profile` +FROM users WHERE user_id = ?', user_x => 'SELECT $$which$$ FROM users W +HERE user = ?', }, ); my $actual_SQL = $sql->offices->sql; my $offices_hash = $sql->offices->selectall_arraryref({Slice=>{}}) # and a final one using the subsitution $offices_hash = $sql->offices_x->(what => "`state`, `zip`")->selectal +l_arraryref({Slice=>{}}) my $user = $sql->user->selectrow_hashref(undef, $userid); $user_profile = $sql->user_x(which => 'profile')->selectrow_hashref(un +def, $userid);


SYNOPSIS

    use DBIx::Library
    my $sql = new DBIx::Library(
                   dbh    => $dbh,
                   quries => { 
                               offices   => 'SELECT * FROM Offices LIMIT $$limit$$',
                             },
                   );
    
    my $offices = $sql->offices->selectall_arrayref({Slice => {}});


DESCRIPTION

For storeing SQL in a central place. The sql is loaded via a hash ref. It would be a trivial matter to load that hashref via XML, ini, or database.

The module takes the list of labeled queries and create methods for each. So if you load a sql statment with the key 'offices' then $sql->offices will return that query. If you have defined dbh in the options then it will return a DIBx::Library::SQL object with that query and database handle setup.

Parameters passed to the method will be substituded when found in the SQL.

     # offices => "SELECT $$what$$ FROM Offices"
     $sql->offices(what => "`address`, `address2`, `city`, `state`");
     # resulting SQL =>  "SELECT `address`, `address2`, `city`, `state` FROM Offices"


OPTIONS

dbh

The database handle to tie SQL statements to. This is optional, but convenient. Since it returns a DBIx::Library::SQL object, that object can then be tied to the database handle.

With dbh defined:

     $sql->offices->do();

With out:


     $dbh->do($sql->offices);

You can see that this saves only a minimal amount of space. However the object returned when dbh defined can be kept around and used repeatedly.

     my $offices = $sql->offices;
     my $office_count = $offices->do();
     my $office_hash  = $offices->selectall_arrayref({Slice=>{}});

delimeter

This sets the delimiter for substitutions. By default it is $$ so any strings inside the query surrounded by $$ are variables.

    my $sql = new DBIx::Library(
                   quries => {offices   => 'SELECT * FROM Offices LIMIT $$limit$$'},
                   );
     $sql->offices(limit => 1);

clean

If clean is set to true then un assigned place holders will be cleared out. Defaults to 1.

    use DBIx::Library
    my $sql = new DBIx::Library(
                   quries => { 
                               offices   => 'SELECT * FROM Offices LIMIT $$limit$$',
                             },
                   clean  => 0,
                   );
                   
    print $sql->offices;
    # prints SELECT * FROM Offices LIMIT $$limit$$
    $sql->{clean} = 1;
    print $sql->offices;
    # prints SELECT * FROM Offices LIMIT 
    
This may or may not be desired behavior depending on the type of query and the circumstances


KNOWN BUGS

None at this time, but we're still looking.



AUTHOR

Eric Hodges <eric256@gmail.com>. Comments, bug reports, and patches are appreciated.

And then the code

package DBIx::Library; use strict; use Carp; use vars qw( $VERSION ); use Data::Dumper; use DBIx::Library::SQL; $VERSION = 0.02; # Initiate a new object: sub new { my $object_or_class = shift; my $class = ref($object_or_class) || $object_or_class; my $options = { @_ }; my $self = { quries => $options->{quries} || {}, default => "SELECT 0;", clean => 1, delimeter => '$$', dbh => $options->{dbh} || undef, }; $self->{clean} = $options->{clean} if exists $options->{clean} +; $self->{default} = $options->{default} if exists $options->{defaul +t}; bless $self, $class; return $self; } sub AUTOLOAD { my $self = shift; my $method = $DBIx::Library::AUTOLOAD; $method =~ s/^DBIx::Library:://; return if $method =~ /DESTROY$/; $self->_get_SQL($method, @_); } sub _get_SQL { my $self = shift; my $method = shift; if (exists $self->{quries}->{$method}) { my $sql = $self->{quries}->{$method}; my $del = qr/\Q$self->{delimeter}\E/; my $params = { @_ }; foreach my $param ( keys %$params ) { my $match = $del . $param .$del; $sql =~ s/$match/$params->{$param}/igs; } $sql =~ s/$del(.+?)$del//g if $self->{clean}; return $self->_wrap_SQL($sql); } return $self->_wrap_SQL($self->{default}); } sub _wrap_SQL { my $self = shift; my $sql = shift; if (defined $self->{dbh}) { return DBIx::Library::SQL->new($sql, $self->{dbh}); } else { return $sql; } } 1;

And then the baby module to go with it

package DBIx::Library::SQL; use overload '""' => sub {return shift->{sql}}; sub new { my $proto = shift; my $class = ref($proto) || $proto; my $sql = shift; my $dbh = shift || undef;; my $self = { dbh => $dbh, sql => $sql, }; bless $self, $class; return $self; } sub sql { my $self = shift; return $self->{sql}; } my @okay = qw/do selectall_arrayref selectall_hashref selectcol_arrayr +ef selectcol_arrayref selectrow_array selectrow_arrayref selectrow_hashref prepare prepare_cached/; our $AUTOLOAD; sub AUTOLOAD { my $self = shift; my $type = ref($self) or die "$self is not an object"; my $name = $AUTOLOAD; $name =~ s/.*://; # strip fully-qualified portion unless (defined $self->{dbh}) { die "Please define the database handle before calling methods" +; } unless (grep {$_ eq $name} @okay ) { die "Bad method"; } if (@_) { return $self->{dbh}->$name($self->{sql}, @_); } else { return $self->{dbh}->$name($self->{sql}); } } 1;

Thanks for any input at all.

Update: some spelling fixes and added reasoning for using this rather than similar modules.


___________
Eric Hodges

Replies are listed 'Best First'.
Re: RFC: DBIx::Library
by blokhead (Monsignor) on Oct 13, 2004 at 18:10 UTC
    The first thing I thought when I saw this was "deja vu!" ;) You should really have a section near the top of the POD explaining what distinguishes your module from other similar ones. I just did a quick search and found DBIx::Librarian, SQL::Catalog, and SQL::Library on CPAN. There are most certainly others. The differences among all of them aren't obvious to me as I've never used any modules like this, and I think a section of the POD addressing this would help potential users who are shopping for a module.

    I do think I like your interface at first glance. It's fairly concise. But I really don't like the syntax needed for:

    # select $$what$$ from offices' $sql->offices(what => "`address`, `address2`, `city`, `state`");
    I think having the module do all the ugly quoting instead would be a little cleaner:
    # select $$what$$ from offices $sql->offices(what => [ qw/address address2 city state/ ]);
    I also don't see any mention of mixing DBI placeholders with your $$foo$$ placeholders. Can I have a query like this:
    # select $$what$$ from offices where office_id=? $sql->offices(what => [qw/name city/])->do($office_id);
    From a skim of the code, it looks as though this is possible -- perhaps an example in the POD is in order, since losing DBI placeholder functionality would be scary.

    And speaking of placeholders, have you given thought to the quoting of your $$foo$$ placeholders? If they quoted, I could use your module to do named placeholders as a complete replacement for DBI's positional placeholders (native RDBMS placeholder performance aside). Maybe you could even use different delimiters to specify different quoting rules (like the backtick-quoted column names above, single-quoted data like DBI placeholders, and non-quoted things like LIMIT clauses). I don't want to give your small module featuritis, but it's some food for thought.

    $POD =~ s/quries/queries/

    blokhead

      Thanks for the input.

      I will add a pod section setting it apart.

      The ugly quoting was a thing for me two, but I hadn't run into it yet in my production environment so it hasn't been added. I'm thinking that if you send an arrayref it quotes and commas it for you to simplify that.

      The placehold scheme it uses is completly independent of the ? scheme. ? is for run time replacements, and this module does.. pre-run replacements? Usefull for actualy changing the sql itslef if you have two sql statments that are 90% the same now you can make all the common parts one entry and use a $$ place holder to insert the extra sql.

      Off to do my homework on those other modules and add an explanation why make this when there is already a wheel.


      ___________
      Eric Hodges
        I'm not sure you understand how important it is to use either placeholders or the DBI quote() method. If you don't do this, you are wide open to SQL-injection attacks.
•Re: RFC: DBIx::Library
by merlyn (Sage) on Oct 13, 2004 at 19:32 UTC

      Hmm I actualy just grabbed that out of perltoot. Is there a better reference than perltoot for this kind of thing? I'd remebered hearing this before (probably from you). I will definitly change the code as that makes it clearer, to be frank I thought that was needed for inheritance in some way and thats why I kept it in.


      ___________
      Eric Hodges
        perltoot is a cargo-cult promulgator. Much better is Damian Conway's book on OO perl. I forget the name.

        Why would you think it's required for inheritance? Have you studied how method dispatch works in Perl5?

        Being right, does not endow the right to be rude; politeness costs nothing.
        Being unknowing, is not the same as being stupid.
        Expressing a contrary opinion, whether to the individual or the group, is more often a sign of deeper thought than of cantankerous belligerence.
        Do not mistake your goals as the only goals; your opinion as the only opinion; your confidence as correctness. Saying you know better is not the same as explaining you know better.

Re: RFC: DBIx::Library
by Juerd (Abbot) on Oct 15, 2004 at 09:21 UTC

    Call me lazy or even simple, but I implement "SQL libraries" using a simple subclass of DBIx::Simple.

    package SmallProject::Database; use base 'DBIx::Simple'; sub connect { my $self = shift; $self->SUPER::connect('dbi:...', 'u', 'p', { RaiseError => 1}); } sub foo { my $self = shift; my ($where, @binds) = $self->abstract->where({ @_ }); $self ->query("SELECT * FROM foo WHERE $where LIMIT 1", @binds) ->hash; } # This could be simpler if SQL::Abstract supported LIMIT clauses: # sub foo { # my $self = shift; # $self->select(foo => '*', { @_ }, \1); # } sub foos { my $self = shift; $self->select(foo => '*', { @_ })->hashes; } 1;
    Somewhere else:
    my $db = SmallProject::Database->new; my $foo = $db->foo(id => 15); my @active_foos = $db->foos(active => 1);

    I really like Class::DBI in many ways, but it is often too slow for what I need. Note by the way that one should never SELECT * and then not request a hash :)

    Juerd # { site => 'juerd.nl', plp_site => 'plp.juerd.nl', do_not_use => 'spamtrap' }

Re: RFC: DBIx::Library
by PodMaster (Abbot) on Oct 14, 2004 at 04:33 UTC
    Below you will find the POD and the modules.
    Thats not pod you got there ( `perldoc perlpod' ), and docs are best kept with the modules.

    MJD says "you can't just make shit up and expect the computer to know what you mean, retardo!"
    I run a Win32 PPM repository for perl 5.6.x and 5.8.x -- I take requests (README).
    ** The third rule of perl club is a statement of fact: pod is sexy.