Class-DBI-Plugin-Backtickify

 view release on metacpan or  search on metacpan

lib/Class/DBI/Plugin/Backtickify.pm  view on Meta::CPAN

our $VERSION = 0.02;

=head1 NAME

Class::DBI::Plugin::Backtickify - surround column and table names with backticks

=head1 SYNOPSIS

    package Film;
    use base qw( Class::DBI ); 
    use Class::DBI::Plugin::Backtickify; # must come after the use base
    
=head1 DESCRIPTION

Puts backticks around table and column names. This allows reserved words to be used 
as column (and table?) names in MySQL (and others?). 

=head1 CAVEATS

It works by installing a C<transform_sql> method into your CDBI class. Other modules and 
plugins maybe do the same thing, in which case they may not play nicely with this. It does  
go through some hoops however to try and call other C<transform_sql> methods, but all the 
replacement tags will already have been removed so this might not help anyway. YMMV.

The installed C<transform_sql> finds column names using a regex over each C<@args> passed in. If 
strings matching column names (but not supposed to represent column names) exist as words 
in the input to the method, they will also get wrapped. Not sure how likely this is.

I haven't tested if this works with joins, but it should.

No tests yet.

=cut

sub import
{
    my ( $class ) = @_;
    
    my $caller = caller( 0 );
    
    no strict 'refs';
    *{"$caller\::transform_sql"} = \&transform_sql;
}

=head1 METHODS

=over 4

=item transform_sql

=back

=cut

sub transform_sql
{
    my ( $self, $sql, @args ) = @_;
    
    #warn "TRANSFORM_SQL: SQL IN:  $sql - @args\n";
    
    # Each entry in @args is a SQL fragment. This will bugger with fragments that 
    # contain strings that match column names but are not supposed to be column names. 
    my $backtickify_arg = sub { $_[0] =~ s/\b$_\b/`$_`/g for $self->all_columns };
    $backtickify_arg->( $_ ) for @args;
    
    # -------------------
    my %cmap;
    my $expand_table = sub {
        my ($class, $alias) = split /=/, shift, 2;
        my $table = $class ? $class->table : $self->table;
        $cmap{ $alias || $table } = $class || ref $self || $self;
        ($alias ||= "") &&= " AS `$alias`";
        return "`$table`$alias";
    };
        
    # -------------------
    my $expand_join = sub {
        my $joins  = shift;
        my @table  = split /\s+/, $joins;
        my %tojoin = map { $table[$_] => $table[ $_ + 1 ] } 0 .. $#table - 1;
        my @sql;
        while (my ($t1, $t2) = each %tojoin) {
                my ($c1, $c2) = map $cmap{$_}
                        || $self->_croak("Don't understand table '$_' in JOIN"), ($t1, $t2);

                my $join_col = sub {
                        my ($c1, $c2) = @_;
                        my $meta = $c1->meta_info('has_a');
                        my ($col) = grep $meta->{$_}->foreign_class eq $c2, keys %$meta;
                        $col;
                };

                my $col = $join_col->($c1 => $c2) || do {
                        ($c1, $c2) = ($c2, $c1);
                        ($t1, $t2) = ($t2, $t1);
                        $join_col->($c1 => $c2);
                };

                $self->_croak("Don't know how to join $c1 to $c2") unless $col;
                push @sql, sprintf " `%s`.`%s` = `%s`.`%s` ", $t1, $col, $t2,
                        $c2->primary_column;
        }
        return join " AND ", @sql;
    };
    
    # -------------------
    $sql =~ s/__TABLE\(?(.*?)\)?__/$expand_table->($1)/eg;
    $sql =~ s/__JOIN\((.*?)\)__/$expand_join->($1)/eg;
    $sql =~ s/__ESSENTIAL__/join ", ", map { "`$_`" } $self->_essential/eg;
    $sql =~ s/__ESSENTIAL\((.*?)\)__/join ", ", map { "`$1`.`$_`" } $self->_essential/eg;    
    
    if ( $sql =~ /__IDENTIFIER__/ ) 
    {
        my $key_sql = join " AND ", map "`$_`=?", $self->primary_columns;
        $sql =~ s/__IDENTIFIER__/$key_sql/g;
    }
    
    # nasty hack
    my $super = ( Class::ISA::super_path( ref( $self ) || $self ) )[0];
    
    my $eval = '{ package %s; $self->SUPER::transform_sql( q(%s), ';



( run in 1.257 second using v1.01-cache-2.11-cpan-364913b4093 )