File Coverage

blib/lib/Jifty/DBI/Handle.pm
Criterion Covered Total %
statement 18 18 100.0
branch n/a
condition n/a
subroutine 6 6 100.0
pod n/a
total 24 24 100.0


line stmt bran cond sub pod time code
1             package Jifty::DBI::Handle;
2 5     5   76124 use strict;
  5         7  
  5         120  
3 5     5   16 use Carp ();
  5         4  
  5         72  
4 5     5   3706 use DBI ();
  5         37862  
  5         114  
5 5     5   2022 use Class::ReturnValue ();
  5         43737  
  5         87  
6 5     5   2885 use Encode ();
  5         41666  
  5         125  
7              
8 5     5   28 use base qw/Jifty::DBI::HasFilters/;
  5         7  
  5         1907  
9              
10             use vars qw(%DBIHandle $PrevHandle $DEBUG $TRANSDEPTH);
11              
12             $TRANSDEPTH = 0;
13              
14             our $VERSION = '0.01';
15              
16             if (my $pattern = $ENV{JIFTY_DBQUERY_CALLER}) {
17             require Hook::LexWrap;
18             Hook::LexWrap::wrap('Jifty::DBI::Handle::simple_query', pre => sub {
19             return unless $_[1] =~ m/$pattern/;
20             warn $_[1].' '.join(',', @_[2..$#_])."\n";
21             Carp::cluck;
22             });
23             }
24              
25             =head1 NAME
26              
27             Jifty::DBI::Handle - Perl extension which is a generic DBI handle
28              
29             =head1 SYNOPSIS
30              
31             use Jifty::DBI::Handle;
32              
33             my $handle = Jifty::DBI::Handle->new();
34             $handle->connect( driver => 'mysql',
35             database => 'dbname',
36             host => 'hostname',
37             user => 'dbuser',
38             password => 'dbpassword');
39             # now $handle isa Jifty::DBI::Handle::mysql
40              
41             =head1 DESCRIPTION
42              
43             This class provides a wrapper for DBI handles that can also perform a
44             number of additional functions.
45              
46             =cut
47              
48             =head2 new
49              
50             Generic constructor
51              
52             =cut
53              
54             sub new {
55             my $proto = shift;
56             my $class = ref($proto) || $proto;
57             my $self = {};
58             bless( $self, $class );
59              
60             @{ $self->{'StatementLog'} } = ();
61             return $self;
62             }
63              
64             =head2 connect PARAMHASH: Driver, Database, Host, User, Password
65              
66             Takes a paramhash and connects to your DBI datasource.
67              
68             If you created the handle with
69             Jifty::DBI::Handle->new
70             and there is a Jifty::DBI::Handle::(Driver) subclass for the driver you have chosen,
71             the handle will be automatically "upgraded" into that subclass.
72              
73             =cut
74              
75             sub connect {
76             my $self = shift;
77              
78             my %args = (
79             driver => undef,
80             database => undef,
81             host => undef,
82             sid => undef,
83             port => undef,
84             user => undef,
85             password => undef,
86             requiressl => undef,
87             @_
88             );
89              
90             if ( $args{'driver'}
91             && !$self->isa( 'Jifty::DBI::Handle::' . $args{'driver'} ) )
92             {
93             if ( $self->_upgrade_handle( $args{'driver'} ) ) {
94             return ( $self->connect(%args) );
95             }
96             }
97              
98             my $dsn = $self->dsn || '';
99              
100             # Setting this actually breaks old RT versions in subtle ways. So we need to explicitly call it
101              
102             $self->build_dsn(%args);
103              
104             # Only connect if we're not connected to this source already
105             if ( ( !$self->dbh ) || ( !$self->dbh->ping ) || ( $self->dsn ne $dsn ) )
106             {
107             my $handle
108             = DBI->connect( $self->dsn, $args{'user'}, $args{'password'} )
109             || Carp::croak "Connect Failed $DBI::errstr\n";
110              
111             #databases do case conversion on the name of columns returned.
112             #actually, some databases just ignore case. this smashes it to something consistent
113             $handle->{FetchHashKeyName} = 'NAME_lc';
114              
115             #Set the handle
116             $self->dbh($handle);
117              
118             return (1);
119             }
120              
121             return (undef);
122              
123             }
124              
125             =head2 _upgrade_handle DRIVER
126              
127             This private internal method turns a plain Jifty::DBI::Handle into one
128             of the standard driver-specific subclasses.
129              
130             =cut
131              
132             sub _upgrade_handle {
133             my $self = shift;
134              
135             my $driver = shift;
136             my $class = 'Jifty::DBI::Handle::' . $driver;
137             eval "require $class";
138             return if $@;
139              
140             bless $self, $class;
141             return 1;
142             }
143              
144             =head2 build_dsn PARAMHASH
145              
146             Builds a dsn suitable for handing to DBI->connect.
147              
148             Mandatory arguments:
149              
150             =over
151              
152             =item driver
153              
154             =item database
155              
156             =back
157              
158             Optional arguments:
159              
160             =over
161              
162             =item host
163              
164             =item port
165              
166             =item sid
167              
168             =item requiressl
169              
170             =item and anything else your DBD lets you pass in
171              
172             =back
173              
174             =cut
175              
176             sub build_dsn {
177             my $self = shift;
178             my %args = (
179             driver => undef,
180             database => undef,
181             host => undef,
182             port => undef,
183             sid => undef,
184             requiressl => undef,
185             @_
186             );
187              
188              
189             my $driver = delete $args{'driver'};
190             $args{'dbname'} ||= delete $args{'database'};
191              
192             $self->{'dsn'} =
193             "dbi:$driver:" . join(';', map { $_ ."=".$args{$_} } grep { defined $args{$_} } keys %args);
194             }
195              
196             =head2 dsn
197              
198             Returns the dsn for this database connection.
199              
200             =cut
201              
202             sub dsn {
203             my $self = shift;
204             return ( $self->{'dsn'} );
205             }
206              
207             =head2 raise_error [MODE]
208              
209             Turns on the Database Handle's RaiseError attribute.
210              
211             =cut
212              
213             sub raise_error {
214             my $self = shift;
215             $self->dbh->{RaiseError} = shift if (@_);
216             return $self->dbh->{RaiseError};
217             }
218              
219             =head2 print_error [MODE]
220              
221             Turns on the Database Handle's PrintError attribute.
222              
223             =cut
224              
225             sub print_error {
226             my $self = shift;
227             $self->dbh->{PrintError} = shift if (@_);
228             return $self->dbh->{PrintError};
229             }
230              
231             =head2 log_sql_statements BOOL
232              
233             Takes a boolean argument. If the boolean is true, it will log all SQL
234             statements, as well as their invocation times and execution times.
235              
236             Returns whether we're currently logging or not as a boolean
237              
238             =cut
239              
240             sub log_sql_statements {
241             my $self = shift;
242             if (@_) {
243             require Time::HiRes;
244             $self->{'_dologsql'} = shift;
245             }
246             return ( $self->{'_dologsql'} );
247             }
248              
249             =head2 _log_sql_statement STATEMENT DURATION
250              
251             add an SQL statement to our query log
252              
253             =cut
254              
255             sub _log_sql_statement {
256             my $self = shift;
257             my $statement = shift;
258             my $duration = shift;
259             my @bind = @_;
260             push @{ $self->{'StatementLog'} },
261             ( [ Time::HiRes::time(), $statement, [@bind], $duration ] );
262              
263             }
264              
265             =head2 clear_sql_statement_log
266              
267             Clears out the SQL statement log.
268              
269             =cut
270              
271             sub clear_sql_statement_log {
272             my $self = shift;
273             @{ $self->{'StatementLog'} } = ();
274             }
275              
276             =head2 sql_statement_log
277              
278             Returns the current SQL statement log as an array of arrays. Each entry is a list of
279              
280             (Time, Statement, [Bindings], Duration)
281              
282             =cut
283              
284             sub sql_statement_log {
285             my $self = shift;
286             return ( @{ $self->{'StatementLog'} } );
287              
288             }
289              
290             =head2 auto_commit [MODE]
291              
292             Turns on the Database Handle's Autocommit attribute.
293              
294             =cut
295              
296             sub auto_commit {
297             my $self = shift;
298              
299             my $mode = 1;
300             $mode = shift if (@_);
301              
302             $self->dbh->{AutoCommit} = $mode;
303             }
304              
305             =head2 disconnect
306              
307             disconnect from your DBI datasource
308              
309             =cut
310              
311             sub disconnect {
312             my $self = shift;
313             if ( $self->dbh ) {
314             return ( $self->dbh->disconnect() );
315             } else {
316             return;
317             }
318             }
319              
320             =head2 dbh [HANDLE]
321              
322             Return the current DBI handle. If we're handed a parameter, make the database handle that.
323              
324             =cut
325              
326             sub dbh {
327             my $self = shift;
328              
329             #If we are setting the database handle, set it.
330             $DBIHandle{$self} = $PrevHandle = shift if (@_);
331              
332             return ( $DBIHandle{$self} ||= $PrevHandle );
333             }
334              
335             =head2 insert $table_NAME @KEY_VALUE_PAIRS
336              
337             Takes a table name and a set of key-value pairs in an array. splits the key value pairs, constructs an INSERT statement and performs the insert. Returns the row_id of this row.
338              
339             =cut
340              
341             sub insert {
342             my ( $self, $table, @pairs ) = @_;
343             my ( @cols, @vals, @bind );
344              
345             #my %seen; #only the *first* value is used - allows drivers to specify default
346             while ( my $key = shift @pairs ) {
347             my $value = shift @pairs;
348              
349             # next if $seen{$key}++;
350             push @cols, $key;
351             push @vals, '?';
352             push @bind, $value;
353             }
354              
355             my $query_string = "INSERT INTO $table ("
356             . CORE::join( ", ", @cols )
357             . ") VALUES " . "("
358             . CORE::join( ", ", @vals ) . ")";
359              
360             my $sth = $self->simple_query( $query_string, @bind );
361             return ($sth);
362             }
363              
364             =head2 update_record_value
365              
366             Takes a hash with columns: Table, Column, Value PrimaryKeys, and
367             IsSQLFunction. Table, and Column should be obvious, Value is where you
368             set the new value you want the column to have. The primary_keys column should
369             be the lvalue of Jifty::DBI::Record::PrimaryKeys(). Finally
370             IsSQLFunction is set when the Value is a SQL function. For example, you
371             might have ('Value'=>'PASSWORD(string)'), by setting IsSQLFunction that
372             string will be inserted into the query directly rather then as a binding.
373              
374             =cut
375              
376             sub update_record_value {
377             my $self = shift;
378             my %args = (
379             table => undef,
380             column => undef,
381             is_sql_function => undef,
382             primary_keys => undef,
383             @_
384             );
385              
386             return 1 unless grep {defined} values %{$args{primary_keys}};
387              
388             my @bind = ();
389             my $query = 'UPDATE ' . $args{'table'} . ' ';
390             $query .= 'SET ' . $args{'column'} . '=';
391              
392             ## Look and see if the column is being updated via a SQL function.
393             if ( $args{'is_sql_function'} ) {
394             $query .= $args{'value'} . ' ';
395             } else {
396             $query .= '? ';
397             push( @bind, $args{'value'} );
398             }
399              
400             ## Constructs the where clause.
401             my $where = 'WHERE ';
402             foreach my $key ( keys %{ $args{'primary_keys'} } ) {
403             $where .= $key . "=?" . " AND ";
404             push( @bind, $args{'primary_keys'}{$key} );
405             }
406             $where =~ s/AND\s$//;
407              
408             my $query_str = $query . $where;
409             return ( $self->simple_query( $query_str, @bind ) );
410             }
411              
412             =head2 update_table_value table COLUMN NEW_value RECORD_ID IS_SQL
413              
414             Update column COLUMN of table table where the record id = RECORD_ID. if IS_SQL is set,
415             don\'t quote the NEW_VALUE
416              
417             =cut
418              
419             sub update_table_value {
420             my $self = shift;
421              
422             ## This is just a wrapper to update_record_value().
423             my %args = ();
424             $args{'table'} = shift;
425             $args{'column'} = shift;
426             $args{'value'} = shift;
427             $args{'primary_keys'} = shift;
428             $args{'is_sql_function'} = shift;
429              
430             return $self->update_record_value(%args);
431             }
432              
433             =head2 simple_query QUERY_STRING, [ BIND_VALUE, ... ]
434              
435             Execute the SQL string specified in QUERY_STRING
436              
437             =cut
438              
439             sub simple_query {
440             my $self = shift;
441             my $query_string = shift;
442             my @bind_values;
443             @bind_values = (@_) if (@_);
444              
445             my $sth = $self->dbh->prepare($query_string);
446             unless ($sth) {
447             if ($DEBUG) {
448             die "$self couldn't prepare the query '$query_string'"
449             . $self->dbh->errstr . "\n";
450             } else {
451             warn "$self couldn't prepare the query '$query_string'"
452             . $self->dbh->errstr . "\n";
453             my $ret = Class::ReturnValue->new();
454             $ret->as_error(
455             errno => '-1',
456             message => "Couldn't prepare the query '$query_string'."
457             . $self->dbh->errstr,
458             do_backtrace => undef
459             );
460             return ( $ret->return_value );
461             }
462             }
463              
464             # Check @bind_values for HASH refs
465             for ( my $bind_idx = 0; $bind_idx < scalar @bind_values; $bind_idx++ ) {
466             if ( ref( $bind_values[$bind_idx] ) eq "HASH" ) {
467             my $bhash = $bind_values[$bind_idx];
468             $bind_values[$bind_idx] = $bhash->{'value'};
469             delete $bhash->{'value'};
470             $sth->bind_param( $bind_idx + 1, undef, $bhash );
471             }
472              
473             # Some databases, such as Oracle fail to cope if it's a perl utf8
474             # string. they desperately want bytes.
475             Encode::_utf8_off( $bind_values[$bind_idx] );
476             }
477              
478             my $basetime;
479             if ( $self->log_sql_statements ) {
480             $basetime = Time::HiRes::time();
481             }
482             my $executed;
483             {
484             no warnings 'uninitialized'; # undef in bind_values makes DBI sad
485             eval { $executed = $sth->execute(@bind_values) };
486             }
487             if ( $self->log_sql_statements ) {
488             $self->_log_sql_statement( $query_string,
489             Time::HiRes::time() - $basetime, @bind_values );
490              
491             }
492              
493             if ( $@ or !$executed ) {
494             if ($DEBUG) {
495             die "$self couldn't execute the query '$query_string'"
496             . $self->dbh->errstr . "\n";
497              
498             } else {
499             # XXX: This warn doesn't show up because we mask logging in Jifty::Test::END.
500             # and it usually fails because the test server is still running.
501             warn "$self couldn't execute the query '$query_string'";
502              
503             my $ret = Class::ReturnValue->new();
504             $ret->as_error(
505             errno => '-1',
506             message => "Couldn't execute the query '$query_string'"
507             . $self->dbh->errstr,
508             do_backtrace => undef
509             );
510             return ( $ret->return_value );
511             }
512              
513             }
514             return ($sth);
515              
516             }
517              
518             =head2 fetch_result QUERY, [ BIND_VALUE, ... ]
519              
520             Takes a SELECT query as a string, along with an array of BIND_VALUEs
521             If the select succeeds, returns the first row as an array.
522             Otherwise, returns a Class::ResturnValue object with the failure loaded
523             up.
524              
525             =cut
526              
527             sub fetch_result {
528             my $self = shift;
529             my $query = shift;
530             my @bind_values = @_;
531             my $sth = $self->simple_query( $query, @bind_values );
532             if ($sth) {
533             return ( $sth->fetchrow );
534             } else {
535             return ($sth);
536             }
537             }
538              
539             =head2 blob_params COLUMN_NAME COLUMN_TYPE
540              
541             Returns a hash ref for the bind_param call to identify BLOB types used
542             by the current database for a particular column type.
543              
544             =cut
545              
546             sub blob_params {
547             my $self = shift;
548              
549             # Don't assign to key 'value' as it is defined later.
550             return ( {} );
551             }
552              
553             =head2 database_version
554              
555             Returns the database's version.
556              
557             If argument C<short> is true returns short variant, in other
558             case returns whatever database handle/driver returns. By default
559             returns short version, e.g. '4.1.23' or '8.0-rc4'.
560              
561             Returns empty string on error or if database couldn't return version.
562              
563             The base implementation uses a C<SELECT VERSION()>
564              
565             =cut
566              
567             sub database_version {
568             my $self = shift;
569             my %args = ( short => 1, @_ );
570              
571             unless ( defined $self->{'database_version'} ) {
572              
573             # turn off error handling, store old values to restore later
574             my $re = $self->raise_error;
575             $self->raise_error(0);
576             my $pe = $self->print_error;
577             $self->print_error(0);
578              
579             my $statement = "SELECT VERSION()";
580             my $sth = $self->simple_query($statement);
581              
582             my $ver = '';
583             $ver = ( $sth->fetchrow_arrayref->[0] || '' ) if $sth;
584             $ver =~ /(\d+(?:\.\d+)*(?:-[a-z0-9]+)?)/i;
585             $self->{'database_version'} = $ver;
586             $self->{'database_version_short'} = $1 || $ver;
587              
588             $self->raise_error($re);
589             $self->print_error($pe);
590             }
591              
592             return $self->{'database_version_short'} if $args{'short'};
593             return $self->{'database_version'};
594             }
595              
596             =head2 case_sensitive
597              
598             Returns 1 if the current database's searches are case sensitive by default
599             Returns undef otherwise
600              
601             =cut
602              
603             sub case_sensitive {
604             my $self = shift;
605             return (1);
606             }
607              
608             =head2 _make_clause_case_insensitive column operator VALUE
609              
610             Takes a column, operator and value. performs the magic necessary to make
611             your database treat this clause as case insensitive.
612              
613             Returns a column operator value triple.
614              
615             =cut
616              
617             sub _case_insensitivity_valid {
618             my $self = shift;
619             my $column = shift;
620             my $operator = shift;
621             my $value = shift;
622              
623             return $value ne ''
624             && $value ne "''"
625             && ( $operator !~ /IS/ && $value !~ /^null$/i )
626             # don't downcase integer values
627             && $value !~ /^['"]?\d+['"]?$/;
628             }
629              
630             sub _make_clause_case_insensitive {
631             my $self = shift;
632             my $column = shift;
633             my $operator = shift;
634             my $value = shift;
635              
636             if ($self->_case_insensitivity_valid($column, $operator, $value)) {
637             $column = "lower($column)";
638             $value = "lower($value)";
639             }
640             return ( $column, $operator, $value );
641             }
642              
643             =head2 begin_transaction
644              
645             Tells Jifty::DBI to begin a new SQL transaction. This will
646             temporarily suspend Autocommit mode.
647              
648             Emulates nested transactions, by keeping a transaction stack depth.
649              
650             =cut
651              
652             sub begin_transaction {
653             my $self = shift;
654             $TRANSDEPTH++;
655             if ( $TRANSDEPTH > 1 ) {
656             return ($TRANSDEPTH);
657             } else {
658             return ( $self->dbh->begin_work );
659             }
660             }
661              
662             =head2 commit
663              
664             Tells Jifty::DBI to commit the current SQL transaction.
665             This will turn Autocommit mode back on.
666              
667             =cut
668              
669             sub commit {
670             my $self = shift;
671             unless ($TRANSDEPTH) {
672             Carp::confess(
673             "Attempted to commit a transaction with none in progress");
674             }
675             $TRANSDEPTH--;
676              
677             if ( $TRANSDEPTH == 0 ) {
678             return ( $self->dbh->commit );
679             } else { #we're inside a transaction
680             return ($TRANSDEPTH);
681             }
682             }
683              
684             =head2 rollback [FORCE]
685              
686             Tells Jifty::DBI to abort the current SQL transaction.
687             This will turn Autocommit mode back on.
688              
689             If this method is passed a true argument, stack depth is blown away and the outermost transaction is rolled back
690              
691             =cut
692              
693             sub rollback {
694             my $self = shift;
695             my $force = shift;
696              
697             my $dbh = $self->dbh;
698             unless ($dbh) {
699             $TRANSDEPTH = 0;
700             return;
701             }
702              
703             #unless ($TRANSDEPTH) {Carp::confess("Attempted to rollback a transaction with none in progress")};
704             if ($force) {
705             $TRANSDEPTH = 0;
706             return ( $dbh->rollback );
707             }
708              
709             $TRANSDEPTH-- if ( $TRANSDEPTH >= 1 );
710             if ( $TRANSDEPTH == 0 ) {
711             return ( $dbh->rollback );
712             } else { #we're inside a transaction
713             return ($TRANSDEPTH);
714             }
715              
716             }
717              
718             =head2 force_rollback
719              
720             Force the handle to rollback. Whether or not we're deep in nested transactions
721              
722             =cut
723              
724             sub force_rollback {
725             my $self = shift;
726             $self->rollback(1);
727             }
728              
729             =head2 transaction_depthh
730              
731             Return the current depth of the faked nested transaction stack.
732              
733             =cut
734              
735             sub transaction_depthh {
736             my $self = shift;
737             return ($TRANSDEPTH);
738             }
739              
740             =head2 apply_limits STATEMENTREF ROWS_PER_PAGE FIRST_ROW
741              
742             takes an SQL SELECT statement and massages it to return ROWS_PER_PAGE starting with FIRST_ROW;
743              
744              
745             =cut
746              
747             sub apply_limits {
748             my $self = shift;
749             my $statementref = shift;
750             my $per_page = shift;
751             my $first = shift;
752              
753             my $limit_clause = '';
754              
755             if ($per_page) {
756             $limit_clause = " LIMIT ";
757             if ($first) {
758             $limit_clause .= $first . ", ";
759             }
760             $limit_clause .= $per_page;
761             }
762              
763             $$statementref .= $limit_clause;
764              
765             }
766              
767             =head2 join { Paramhash }
768              
769             Takes a paramhash of everything Jifty::DBI::Collection's C<join> method
770             takes, plus a parameter called C<collection> that contains a ref to a
771             L<Jifty::DBI::Collection> object'.
772              
773             This performs the join.
774              
775              
776             =cut
777              
778             sub join {
779              
780             my $self = shift;
781             my %args = (
782             collection => undef,
783             type => 'normal',
784             column1 => 'main',
785             alias1 => undef,
786             table2 => undef,
787             column2 => undef,
788             alias2 => undef,
789             expression => undef,
790             operator => '=',
791             @_
792             );
793              
794             my $string;
795              
796             my $alias;
797              
798             #If we're handed in an alias2, we need to go remove it from the
799             # Aliases array. Basically, if anyone generates an alias and then
800             # tries to use it in a join later, we want to be smart about creating
801             # joins, so we need to go rip it out of the old aliases table and drop
802             # it in as an explicit join
803             if ( $args{'alias2'} ) {
804              
805             # this code is slow and wasteful, but it's clear.
806             my @aliases = @{ $args{'collection'}->{'aliases'} };
807             my @new_aliases;
808             foreach my $old_alias (@aliases) {
809             if ( $old_alias =~ /^(.*?) ($args{'alias2'})$/ ) {
810             $args{'table2'} = $1;
811             $alias = $2;
812              
813             } else {
814             push @new_aliases, $old_alias;
815             }
816             }
817              
818             # If we found an alias, great. let's just pull out the table and alias for the other item
819             unless ($alias) {
820              
821             # if we can't do that, can we reverse the join and have it work?
822             my $a1 = $args{'alias1'};
823             my $f1 = $args{'column1'};
824             $args{'alias1'} = $args{'alias2'};
825             $args{'column1'} = $args{'column2'};
826             $args{'alias2'} = $a1;
827             $args{'column2'} = $f1;
828              
829             @aliases = @{ $args{'collection'}->{'aliases'} };
830             @new_aliases = ();
831             foreach my $old_alias (@aliases) {
832             if ( $old_alias =~ /^(.*?) ($args{'alias2'})$/ ) {
833             $args{'table2'} = $1;
834             $alias = $2;
835              
836             } else {
837             push @new_aliases, $old_alias;
838             }
839             }
840              
841             }
842              
843             if ( !$alias || $args{'alias1'} ) {
844             return ( $self->_normal_join(%args) );
845             }
846              
847             $args{'collection'}->{'aliases'} = \@new_aliases;
848             }
849              
850             else {
851             $alias = $args{'collection'}->_get_alias( $args{'table2'} );
852              
853             }
854              
855             if ( $args{'type'} =~ /LEFT/i ) {
856              
857             $string = " LEFT JOIN " . $args{'table2'} . " $alias ";
858              
859             } else {
860              
861             $string = " JOIN " . $args{'table2'} . " $alias ";
862              
863             }
864              
865             my $criterion;
866             if ( $args{'expression'} ) {
867             $criterion = $args{'expression'};
868             } else {
869             $criterion = $args{'alias1'} . "." . $args{'column1'};
870             }
871              
872             $args{'collection'}->{'leftjoins'}{"$alias"}{'alias_string'} = $string;
873             $args{'collection'}->{'leftjoins'}{"$alias"}{'entry_aggregator'}
874             = $args{'entry_aggregator'}
875             if ( $args{'entry_aggregator'} );
876             $args{'collection'}->{'leftjoins'}{"$alias"}{'depends_on'}
877             = $args{'alias1'};
878             $args{'collection'}->{'leftjoins'}{"$alias"}{'criteria'}
879             { 'criterion' . $args{'collection'}->{'criteria_count'}++ }
880             = " $criterion $args{'operator'} $alias.$args{'column2'}";
881              
882             return ($alias);
883             }
884              
885             sub _normal_join {
886              
887             my $self = shift;
888             my %args = (
889             collection => undef,
890             type => 'normal',
891             column1 => undef,
892             alias1 => undef,
893             table2 => undef,
894             column2 => undef,
895             alias2 => undef,
896             operator => '=',
897             @_
898             );
899              
900             my $sb = $args{'collection'};
901              
902             if ( $args{'type'} =~ /LEFT/i ) {
903             my $alias = $sb->_get_alias( $args{'table2'} );
904              
905             $sb->{'leftjoins'}{"$alias"}{'alias_string'}
906             = " LEFT JOIN $args{'table2'} $alias ";
907              
908             $sb->{'leftjoins'}{"$alias"}{'criteria'}{'base_criterion'}
909             = " $args{'alias1'}.$args{'column1'} $args{'operator'} $alias.$args{'column2'}";
910              
911             return ($alias);
912             } else {
913             $sb->Jifty::DBI::Collection::limit(
914             entry_aggregator => 'AND',
915             quote_value => 0,
916             alias => $args{'alias1'},
917             column => $args{'column1'},
918             value => $args{'alias2'} . "." . $args{'column2'},
919             @_
920             );
921             }
922             }
923              
924             # this code is all hacky and evil. but people desperately want _something_ and I'm
925             # super tired. refactoring gratefully appreciated.
926              
927             sub _build_joins {
928             my $self = shift;
929             my $sb = shift;
930             my %seen_aliases;
931              
932             $seen_aliases{'main'} = 1;
933              
934             # We don't want to get tripped up on a dependency on a simple alias.
935             foreach my $alias ( @{ $sb->{'aliases'} } ) {
936             if ( $alias =~ /^(.*?)\s+(.*?)$/ ) {
937             $seen_aliases{$2} = 1;
938             }
939             }
940              
941             my $join_clause = $sb->table . " main ";
942              
943             my @keys = ( keys %{ $sb->{'leftjoins'} } );
944             my %seen;
945              
946             while ( my $join = shift @keys ) {
947             if ( !$sb->{'leftjoins'}{$join}{'depends_on'}
948             || $seen_aliases{ $sb->{'leftjoins'}{$join}{'depends_on'} } )
949             {
950             $join_clause
951             .= $sb->{'leftjoins'}{$join}{'alias_string'} . " ON ";
952              
953             my @criteria = values %{ $sb->{'leftjoins'}{$join}{'criteria'} };
954             my $entry_aggregator
955             = $sb->{'leftjoins'}{$join}{'entry_aggregator'} || 'AND';
956             my $criteria = CORE::join( " $entry_aggregator ",
957             map {" ( $_ ) "} @criteria );
958              
959             $join_clause .= "( " . $criteria . " ) ";
960             $join_clause = "(" . $join_clause . ")";
961              
962             $seen_aliases{$join} = 1;
963             } else {
964             push( @keys, $join );
965             die "Unsatisfied dependency chain in joins @keys"
966             if $seen{"@keys"}++;
967             }
968              
969             }
970             return ( CORE::join( ", ", ( $join_clause, @{ $sb->{'aliases'} } ) ) );
971              
972             }
973              
974             =head2 distinct_query STATEMENTREF
975              
976             takes an incomplete SQL SELECT statement and massages it to return a DISTINCT result set.
977              
978              
979             =cut
980              
981             sub distinct_query {
982             my $self = shift;
983             my $statementref = shift;
984             my $sb = shift;
985              
986             # Prepend select query for DBs which allow DISTINCT on all column types.
987             $$statementref = "SELECT DISTINCT ".$sb->_preload_columns." FROM $$statementref";
988              
989             $$statementref .= $sb->_group_clause;
990             $$statementref .= $sb->_order_clause;
991             }
992              
993             =head2 distinct_count STATEMENTREF
994              
995             takes an incomplete SQL SELECT statement and massages it to return a DISTINCT result set.
996              
997              
998             =cut
999              
1000             sub distinct_count {
1001             my $self = shift;
1002             my $statementref = shift;
1003              
1004             # Prepend select query for DBs which allow DISTINCT on all column types.
1005             $$statementref = "SELECT COUNT(DISTINCT main.id) FROM $$statementref";
1006              
1007             }
1008              
1009             =head2 Log MESSAGE
1010              
1011             Takes a single argument, a message to log.
1012              
1013             Currently prints that message to STDERR
1014              
1015             =cut
1016              
1017             sub log {
1018             my $self = shift;
1019             my $msg = shift;
1020             warn $msg . "\n";
1021              
1022             }
1023              
1024             =head2 DESTROY
1025              
1026             When we get rid of the L<Jifty::DBI::Handle>, we need to disconnect
1027             from the database
1028              
1029             =cut
1030              
1031             sub DESTROY {
1032             my $self = shift;
1033             $self->disconnect;
1034             delete $DBIHandle{$self};
1035             }
1036              
1037             1;
1038             __END__
1039              
1040              
1041             =head1 DIAGNOSIS
1042              
1043             Setting C<JIFTY_DBQUERY_CALLER> environment variable will make
1044             L<Jifty::DBI> dump the caller for the SQL queries matching it. See
1045             also C<DBI> about setting C<DBI_PROFILE>.
1046              
1047             =head1 AUTHOR
1048              
1049             Jesse Vincent, jesse@fsck.com
1050              
1051             =head1 SEE ALSO
1052              
1053             perl(1), L<Jifty::DBI>
1054              
1055             =cut
1056