File Coverage

blib/lib/DBIx/SearchBuilder/Handle.pm
Criterion Covered Total %
statement 430 673 63.8
branch 154 286 53.8
condition 44 91 48.3
subroutine 50 74 67.5
pod 49 53 92.4
total 727 1177 61.7


line stmt bran cond sub pod time code
1             package DBIx::SearchBuilder::Handle;
2              
3 25     25   352618 use strict;
  25         56  
  25         928  
4 25     25   139 use warnings;
  25         52  
  25         1300  
5              
6 25     25   137 use Carp qw(croak cluck);
  25         51  
  25         1897  
7 25     25   7581 use DBI;
  25         86226  
  25         1441  
8 25     25   5086 use Class::ReturnValue;
  25         127776  
  25         3082  
9 25     25   3443 use Encode qw();
  25         85665  
  25         741  
10 25     25   3322 use version;
  25         12344  
  25         185  
11              
12 25     25   6324 use DBIx::SearchBuilder::Util qw/ sorted_values /;
  25         54  
  25         1707  
13              
14 25     25   166 use vars qw(@ISA %DBIHandle $PrevHandle $DEBUG %TRANSDEPTH %TRANSROLLBACK %FIELDS_IN_TABLE);
  25         54  
  25         68390  
15              
16              
17             =head1 NAME
18              
19             DBIx::SearchBuilder::Handle - Perl extension which is a generic DBI handle
20              
21             =head1 SYNOPSIS
22              
23             use DBIx::SearchBuilder::Handle;
24              
25             my $handle = DBIx::SearchBuilder::Handle->new();
26             $handle->Connect( Driver => 'mysql',
27             Database => 'dbname',
28             Host => 'hostname',
29             User => 'dbuser',
30             Password => 'dbpassword');
31             # now $handle isa DBIx::SearchBuilder::Handle::mysql
32              
33             =head1 DESCRIPTION
34              
35             This class provides a wrapper for DBI handles that can also perform a number of additional functions.
36              
37             =cut
38              
39              
40              
41             =head2 new
42              
43             Generic constructor
44              
45             =cut
46              
47             sub new {
48 23     23 1 420580 my $proto = shift;
49 23   33     206 my $class = ref($proto) || $proto;
50 23         108 my $self = {};
51 23         54 bless ($self, $class);
52              
53             # Enable quotes table names
54 23         108 my %args = ( QuoteTableNames => 0, @_ );
55 23         198 $self->{'QuoteTableNames'} = $args{QuoteTableNames};
56              
57 23         55 @{$self->{'StatementLog'}} = ();
  23         68  
58 23         127 return $self;
59             }
60              
61              
62              
63             =head2 Connect PARAMHASH: Driver, Database, Host, User, Password, QuoteTableNames
64              
65             Takes a paramhash and connects to your DBI datasource.
66              
67             You should _always_ set
68              
69             DisconnectHandleOnDestroy => 1
70              
71             unless you have a legacy app like RT2 or RT 3.0.{0,1,2} that depends on the broken behaviour.
72              
73             If you created the handle with
74             DBIx::SearchBuilder::Handle->new
75             and there is a DBIx::SearchBuilder::Handle::(Driver) subclass for the driver you have chosen,
76             the handle will be automatically "upgraded" into that subclass.
77              
78             QuoteTableNames option will force all table names to be quoted if the driver subclass has a method
79             for quoting implemented. The mysql subclass will detect mysql version 8 and set the flag.
80              
81             =cut
82              
83             sub Connect {
84 22     22 1 20973 my $self = shift;
85 22         230 my %args = (
86             Driver => undef,
87             Database => undef,
88             Host => undef,
89             SID => undef,
90             Port => undef,
91             User => undef,
92             Password => undef,
93             RequireSSL => undef,
94             DisconnectHandleOnDestroy => undef,
95             QuoteTableNames => undef,
96             @_
97             );
98              
99 22 100 66     294 if ( $args{'Driver'} && !$self->isa( __PACKAGE__ .'::'. $args{'Driver'} ) ) {
100 1 50       7 return $self->Connect( %args ) if $self->_UpgradeHandle( $args{'Driver'} );
101             }
102              
103             # Setting this actually breaks old RT versions in subtle ways.
104             # So we need to explicitly call it
105 21         62 $self->{'DisconnectHandleOnDestroy'} = $args{'DisconnectHandleOnDestroy'};
106              
107             # Enable optional quoted table names
108 21 50       119 $self->{'QuoteTableNames'} = delete $args{QuoteTableNames} if defined $args{QuoteTableNames};
109              
110 21   50     132 my $old_dsn = $self->DSN || '';
111 21         161 my $new_dsn = $self->BuildDSN( %args );
112              
113             # Only connect if we're not connected to this source already
114 21 0 33     117 return undef if $self->dbh && $self->dbh->ping && $new_dsn eq $old_dsn;
      33        
115              
116             my $handle = DBI->connect(
117 21 50       203 $new_dsn, $args{'User'}, $args{'Password'}
118             ) or croak "Connect Failed $DBI::errstr\n";
119              
120             # databases do case conversion on the name of columns returned.
121             # actually, some databases just ignore case. this smashes it to something consistent
122 21         51641 $handle->{FetchHashKeyName} ='NAME_lc';
123              
124             # Set the handle
125 21         179 $self->dbh($handle);
126              
127             # Cache version info
128 21         115 $self->DatabaseVersion;
129              
130             # force quoted tables for mysql 8
131 21 50       243 $self->{'QuoteTableNames'} = 1 if $self->_RequireQuotedTables;
132              
133 21         138 return 1;
134             }
135              
136              
137             =head2 _UpgradeHandle DRIVER
138              
139             This private internal method turns a plain DBIx::SearchBuilder::Handle into one
140             of the standard driver-specific subclasses.
141              
142             =cut
143              
144             sub _UpgradeHandle {
145 1     1   2 my $self = shift;
146              
147 1         3 my $driver = shift;
148 1         2 my $class = 'DBIx::SearchBuilder::Handle::' . $driver;
149 1         3 local $@;
150 1         102 eval "require $class";
151 1 50       7 return if $@;
152              
153 1         3 bless $self, $class;
154 1         18 return 1;
155             }
156              
157              
158             =head2 BuildDSN PARAMHASH
159              
160             Takes a bunch of parameters:
161              
162             Required: Driver, Database,
163             Optional: Host, Port and RequireSSL
164              
165             Builds a DSN suitable for a DBI connection
166              
167             =cut
168              
169             sub BuildDSN {
170 21     21 1 41 my $self = shift;
171 21         131 my %args = (
172             Driver => undef,
173             Database => undef,
174             Host => undef,
175             Port => undef,
176             SID => undef,
177             RequireSSL => undef,
178             @_
179             );
180              
181 21         63 my $dsn = "dbi:$args{'Driver'}:dbname=$args{'Database'}";
182 21 50       79 $dsn .= ";sid=$args{'SID'}" if $args{'SID'};
183 21 50       83 $dsn .= ";host=$args{'Host'}" if $args{'Host'};
184 21 50       180 $dsn .= ";port=$args{'Port'}" if $args{'Port'};
185 21 50       137 $dsn .= ";requiressl=1" if $args{'RequireSSL'};
186              
187 21         116 return $self->{'dsn'} = $dsn;
188             }
189              
190              
191             =head2 DSN
192              
193             Returns the DSN for this database connection.
194              
195             =cut
196              
197             sub DSN {
198 21     21 1 142 return shift->{'dsn'};
199             }
200              
201              
202              
203             =head2 RaiseError [MODE]
204              
205             Turns on the Database Handle's RaiseError attribute.
206              
207             =cut
208              
209             sub RaiseError {
210 0     0 1 0 my $self = shift;
211              
212 0         0 my $mode = 1;
213 0 0       0 $mode = shift if (@_);
214              
215 0         0 $self->dbh->{RaiseError}=$mode;
216             }
217              
218              
219              
220              
221             =head2 PrintError [MODE]
222              
223             Turns on the Database Handle's PrintError attribute.
224              
225             =cut
226              
227             sub PrintError {
228 0     0 1 0 my $self = shift;
229              
230 0         0 my $mode = 1;
231 0 0       0 $mode = shift if (@_);
232              
233 0         0 $self->dbh->{PrintError}=$mode;
234             }
235              
236              
237              
238             =head2 LogSQLStatements BOOL
239              
240             Takes a boolean argument. If the boolean is true, SearchBuilder will log all SQL
241             statements, as well as their invocation times and execution times.
242              
243             Returns whether we're currently logging or not as a boolean
244              
245             =cut
246              
247             sub LogSQLStatements {
248 1056     1056 1 2146 my $self = shift;
249 1056 50       3236 if (@_) {
250 0         0 require Time::HiRes;
251 0         0 $self->{'_DoLogSQL'} = shift;
252             }
253 1056         3985 return ($self->{'_DoLogSQL'});
254             }
255              
256             =head2 _LogSQLStatement STATEMENT DURATION
257              
258             Add an SQL statement to our query log
259              
260             =cut
261              
262             sub _LogSQLStatement {
263 0     0   0 my $self = shift;
264 0         0 my $statement = shift;
265 0         0 my $duration = shift;
266 0         0 my @bind = @_;
267 0         0 push @{$self->{'StatementLog'}} , ([Time::HiRes::time(), $statement, [@bind], $duration, Carp::longmess("Executed SQL query")]);
  0         0  
268              
269             }
270              
271             =head2 ClearSQLStatementLog
272              
273             Clears out the SQL statement log.
274              
275              
276             =cut
277              
278             sub ClearSQLStatementLog {
279 0     0 1 0 my $self = shift;
280 0         0 @{$self->{'StatementLog'}} = ();
  0         0  
281             }
282              
283              
284             =head2 SQLStatementLog
285              
286             Returns the current SQL statement log as an array of arrays. Each entry is a triple of
287              
288             (Time, Statement, Duration)
289              
290             =cut
291              
292             sub SQLStatementLog {
293 0     0 1 0 my $self = shift;
294 0         0 return (@{$self->{'StatementLog'}});
  0         0  
295              
296             }
297              
298              
299              
300             =head2 AutoCommit [MODE]
301              
302             Turns on the Database Handle's AutoCommit attribute.
303              
304             =cut
305              
306             sub AutoCommit {
307 0     0 1 0 my $self = shift;
308              
309 0         0 my $mode = 1;
310 0 0       0 $mode = shift if (@_);
311              
312 0         0 $self->dbh->{AutoCommit}=$mode;
313             }
314              
315              
316              
317              
318             =head2 Disconnect
319              
320             Disconnect from your DBI datasource
321              
322             =cut
323              
324             sub Disconnect {
325 0     0 1 0 my $self = shift;
326 0         0 my $dbh = $self->dbh;
327 0 0       0 return unless $dbh;
328 0         0 $self->Rollback(1);
329              
330 0         0 my $ret = $dbh->disconnect;
331              
332             # DBD::mysql with MariaDB 10.2+ could cause segment faults when
333             # interacting with a disconnected handle, here we unset
334             # dbh to inform other code that there is no connection any more.
335             # See also https://github.com/perl5-dbi/DBD-mysql/issues/306
336 0   0     0 my ($version) = ( $self->DatabaseVersion // '' ) =~ /^(\d+\.\d+)/;
337 0 0 0     0 if ( $self->isa('DBIx::SearchBuilder::Handle::mysql')
      0        
338             && $self->{'database_version'} =~ /mariadb/i
339             && version->parse('v'.$version) > version->parse('v10.2') )
340             {
341 0         0 $self->dbh(undef);
342             }
343              
344 0         0 return $ret;
345             }
346              
347              
348             =head2 dbh [HANDLE]
349              
350             Return the current DBI handle. If we're handed a parameter, make the database handle that.
351              
352             =cut
353              
354             # allow use of Handle as a synonym for DBH
355             *Handle=\&dbh;
356              
357             sub dbh {
358 986     986 1 3843 my $self=shift;
359              
360             #If we are setting the database handle, set it.
361 986 100       2528 if ( @_ ) {
362 21         80 $DBIHandle{$self} = $PrevHandle = shift;
363 21         69 %FIELDS_IN_TABLE = ();
364             }
365              
366 986   66     13227 return($DBIHandle{$self} ||= $PrevHandle);
367             }
368              
369              
370             =head2 Insert $TABLE_NAME @KEY_VALUE_PAIRS
371              
372             Takes a table name and a set of key-value pairs in an array.
373             Splits the key value pairs, constructs an INSERT statement
374             and performs the insert.
375              
376             Base class return statement handle object, while DB specific
377             subclass should return row id.
378              
379             =cut
380              
381             sub Insert {
382 155     155 1 328 my $self = shift;
383 155         818 return $self->SimpleQuery( $self->InsertQueryString(@_) );
384             }
385              
386             =head2 InsertQueryString $TABLE_NAME @KEY_VALUE_PAIRS
387              
388             Takes a table name and a set of key-value pairs in an array.
389             Splits the key value pairs, constructs an INSERT statement
390             and returns query string and set of bind values.
391              
392             This method is more useful for subclassing in DB specific
393             handles. L method is preferred for end users.
394              
395             =cut
396              
397             sub InsertQueryString {
398 155     155 1 695 my($self, $table, @pairs) = @_;
399 155         529 my(@cols, @vals, @bind);
400              
401 155         675 while ( my $key = shift @pairs ) {
402 410         808 push @cols, $key;
403 410         745 push @vals, '?';
404 410         1334 push @bind, shift @pairs;
405             }
406              
407 155 50       692 $table = $self->QuoteName($table) if $self->QuoteTableNames;
408 155         425 my $QueryString = "INSERT INTO $table";
409 155         692 $QueryString .= " (". join(", ", @cols) .")";
410 155         597 $QueryString .= " VALUES (". join(", ", @vals). ")";
411 155         1077 return ($QueryString, @bind);
412             }
413              
414             =head2 InsertFromSelect
415              
416             Takes table name, array reference with columns, select query
417             and list of bind values. Inserts data select by the query
418             into the table.
419              
420             To make sure call is portable every column in result of
421             the query should have unique name or should be aliased.
422             See L for
423             details.
424              
425             =cut
426              
427             sub InsertFromSelect {
428 4     4 1 1164 my ($self, $table, $columns, $query, @binds) = @_;
429              
430 4 50       27 $columns = join ', ', @$columns
431             if $columns;
432              
433 4 50       26 $table = $self->QuoteName($table) if $self->{'QuoteTableNames'};
434 4         13 my $full_query = "INSERT INTO $table";
435 4 50       19 $full_query .= " ($columns)" if $columns;
436 4         13 $full_query .= ' '. $query;
437 4         19 my $sth = $self->SimpleQuery( $full_query, @binds );
438 4 50       55 return $sth unless $sth;
439              
440 4         65 my $rows = $sth->rows;
441 4 50       177 return $rows == 0? '0E0' : $rows;
442             }
443              
444             =head2 UpdateRecordValue
445              
446             Takes a hash with fields: Table, Column, Value PrimaryKeys, and
447             IsSQLFunction. Table, and Column should be obvious, Value is where you
448             set the new value you want the column to have. The primary_keys field should
449             be the lvalue of DBIx::SearchBuilder::Record::PrimaryKeys(). Finally
450             IsSQLFunction is set when the Value is a SQL function. For example, you
451             might have ('Value'=>'PASSWORD(string)'), by setting IsSQLFunction that
452             string will be inserted into the query directly rather then as a binding.
453              
454             =cut
455              
456             sub UpdateRecordValue {
457 19     19 1 59 my $self = shift;
458 19         134 my %args = ( Table => undef,
459             Column => undef,
460             IsSQLFunction => undef,
461             PrimaryKeys => undef,
462             @_ );
463              
464 19         42 my @bind = ();
465 19 50       91 $args{Table} = $self->QuoteName($args{Table}) if $self->{'QuoteTableNames'};
466 19         52 my $query = 'UPDATE ' . $args{'Table'} . ' ';
467 19         50 $query .= 'SET ' . $args{'Column'} . '=';
468              
469             ## Look and see if the field is being updated via a SQL function.
470 19 50       84 if ($args{'IsSQLFunction'}) {
471 0         0 $query .= $args{'Value'} . ' ';
472             }
473             else {
474 19         43 $query .= '? ';
475 19         67 push (@bind, $args{'Value'});
476             }
477              
478             ## Constructs the where clause.
479 19         38 my $where = 'WHERE ';
480 19         37 foreach my $key (sort keys %{$args{'PrimaryKeys'}}) {
  19         123  
481 19         46 $where .= $key . "=?" . " AND ";
482 19         57 push (@bind, $args{'PrimaryKeys'}{$key});
483             }
484 19         129 $where =~ s/AND\s$//;
485              
486 19         57 my $query_str = $query . $where;
487 19         78 return ($self->SimpleQuery($query_str, @bind));
488             }
489              
490              
491              
492              
493             =head2 UpdateTableValue TABLE COLUMN NEW_VALUE RECORD_ID IS_SQL
494              
495             Update column COLUMN of table TABLE where the record id = RECORD_ID. if IS_SQL is set,
496             don\'t quote the NEW_VALUE
497              
498             =cut
499              
500             sub UpdateTableValue {
501 0     0 1 0 my $self = shift;
502              
503             ## This is just a wrapper to UpdateRecordValue().
504 0         0 my %args = ();
505 0         0 $args{'Table'} = shift;
506 0         0 $args{'Column'} = shift;
507 0         0 $args{'Value'} = shift;
508 0         0 $args{'PrimaryKeys'} = shift;
509 0         0 $args{'IsSQLFunction'} = shift;
510              
511 0         0 return $self->UpdateRecordValue(%args)
512             }
513              
514             =head1 SimpleUpdateFromSelect
515              
516             Takes table name, hash reference with (column, value) pairs,
517             select query and list of bind values.
518              
519             Updates the table, but only records with IDs returned by the
520             selected query, eg:
521              
522             UPDATE $table SET %values WHERE id IN ( $query )
523              
524             It's simple as values are static and search only allowed
525             by id.
526              
527             =cut
528              
529             sub SimpleUpdateFromSelect {
530 1     1 0 1029 my ($self, $table, $values, $query, @query_binds) = @_;
531              
532 1         3 my @columns; my @binds;
533 1         7 for my $k (sort keys %$values) {
534 2         7 push @columns, $k;
535 2         5 push @binds, $values->{$k};
536             }
537              
538 1 50       6 $table = $self->QuoteName($table) if $self->{'QuoteTableNames'};
539 1         3 my $full_query = "UPDATE $table SET ";
540 1         8 $full_query .= join ', ', map "$_ = ?", @columns;
541 1         4 $full_query .= ' WHERE id IN ('. $query .')';
542 1         6 my $sth = $self->SimpleQuery( $full_query, @binds, @query_binds );
543 1 50       9 return $sth unless $sth;
544              
545 1         14 my $rows = $sth->rows;
546 1 50       68 return $rows == 0? '0E0' : $rows;
547             }
548              
549             =head1 DeleteFromSelect
550              
551             Takes table name, select query and list of bind values.
552              
553             Deletes from the table, but only records with IDs returned by the
554             select query, eg:
555              
556             DELETE FROM $table WHERE id IN ($query)
557              
558             =cut
559              
560             sub DeleteFromSelect {
561 1     1 0 18 my ($self, $table, $query, @binds) = @_;
562 1 50       7 $table = $self->QuoteName($table) if $self->{'QuoteTableNames'};
563 1         6 my $sth = $self->SimpleQuery(
564             "DELETE FROM $table WHERE id IN ($query)",
565             @binds
566             );
567 1 50       8 return $sth unless $sth;
568              
569 1         16 my $rows = $sth->rows;
570 1 50       36 return $rows == 0? '0E0' : $rows;
571             }
572              
573             =head2 SimpleQuery QUERY_STRING, [ BIND_VALUE, ... ]
574              
575             Execute the SQL string specified in QUERY_STRING
576              
577             =cut
578              
579             sub SimpleQuery {
580 530     530 1 1929 my $self = shift;
581 530         976 my $QueryString = shift;
582 530         998 my @bind_values;
583 530 100       1680 @bind_values = (@_) if (@_);
584              
585 530         1618 my $sth = $self->dbh->prepare($QueryString);
586 530 100       73184 unless ($sth) {
587 2 50       7 if ($DEBUG) {
588 0         0 die "$self couldn't prepare the query '$QueryString'"
589             . $self->dbh->errstr . "\n";
590             }
591             else {
592 2         13 warn "$self couldn't prepare the query '$QueryString'"
593             . $self->dbh->errstr . "\n";
594 2         20 my $ret = Class::ReturnValue->new();
595 2         16 $ret->as_error(
596             errno => '-1',
597             message => "Couldn't prepare the query '$QueryString'."
598             . $self->dbh->errstr,
599             do_backtrace => undef
600             );
601 2         50 return ( $ret->return_value );
602             }
603             }
604              
605             # Check @bind_values for HASH refs. These can be sent as type settings
606             # or hits for the DBD module for dealing with certain data types,
607             # usually BLOBS.
608 528         2203 for ( my $bind_idx = 0 ; $bind_idx < scalar @bind_values ; $bind_idx++ ) {
609 553 50       2184 if ( ref( $bind_values[$bind_idx] ) eq "HASH" ) {
610 0         0 my $bhash = $bind_values[$bind_idx];
611 0         0 $bind_values[$bind_idx] = $bhash->{'value'};
612 0         0 delete $bhash->{'value'};
613              
614             # DBD::MariaDB only works using the string version of setting type,
615             # so convert the hash to the string for this case.
616 0 0 0     0 if ( $self->isa('DBIx::SearchBuilder::Handle::MariaDB')
      0        
617             && $bhash->{'TYPE'}
618             && $bhash->{'TYPE'} eq 'SQL_BLOB' ) {
619 0         0 $sth->bind_param( $bind_idx + 1, undef, DBI::SQL_BLOB );
620             }
621             else {
622 0         0 $sth->bind_param( $bind_idx + 1, undef, $bhash );
623             }
624             }
625             }
626              
627 528         958 my $basetime;
628 528 50       1712 if ( $self->LogSQLStatements ) {
629 0         0 $basetime = Time::HiRes::time();
630             }
631 528         922 my $executed;
632             {
633 25     25   211 no warnings 'uninitialized' ; # undef in bind_values makes DBI sad
  25         96  
  25         166400  
  528         899  
634 528         1054 eval { $executed = $sth->execute(@bind_values) };
  528         5006733  
635             }
636 528 50       4093 if ( $self->LogSQLStatements ) {
637 0         0 $self->_LogSQLStatement( $QueryString, Time::HiRes::time() - $basetime, @bind_values );
638             }
639              
640 528 50 33     3523 if ( $@ or !$executed ) {
641 0 0       0 if ($DEBUG) {
642 0         0 die "$self couldn't execute the query '$QueryString'"
643             . $self->dbh->errstr . "\n";
644              
645             }
646             else {
647 0         0 cluck "$self couldn't execute the query '$QueryString'";
648              
649 0         0 my $ret = Class::ReturnValue->new();
650 0         0 $ret->as_error(
651             errno => '-1',
652             message => "Couldn't execute the query '$QueryString'"
653             . $self->dbh->errstr,
654             do_backtrace => undef
655             );
656 0         0 return ( $ret->return_value );
657             }
658              
659             }
660 528         4234 return ($sth);
661              
662             }
663              
664              
665              
666             =head2 FetchResult QUERY, [ BIND_VALUE, ... ]
667              
668             Takes a SELECT query as a string, along with an array of BIND_VALUEs
669             If the select succeeds, returns the first row as an array.
670             Otherwise, returns a Class::ResturnValue object with the failure loaded
671             up.
672              
673             =cut
674              
675             sub FetchResult {
676 0     0 1 0 my $self = shift;
677 0         0 my $query = shift;
678 0         0 my @bind_values = @_;
679 0         0 my $sth = $self->SimpleQuery($query, @bind_values);
680 0 0       0 if ($sth) {
681 0         0 return ($sth->fetchrow);
682             }
683             else {
684 0         0 return($sth);
685             }
686             }
687              
688              
689             =head2 BinarySafeBLOBs
690              
691             Returns 1 if the current database supports BLOBs with embedded nulls.
692             Returns undef if the current database doesn't support BLOBs with embedded nulls
693              
694             =cut
695              
696             sub BinarySafeBLOBs {
697 0     0 1 0 my $self = shift;
698 0         0 return(1);
699             }
700              
701              
702              
703             =head2 KnowsBLOBs
704              
705             Returns 1 if the current database supports inserts of BLOBs automatically.
706             Returns undef if the current database must be informed of BLOBs for inserts.
707              
708             =cut
709              
710             sub KnowsBLOBs {
711 174     174 1 380 my $self = shift;
712 174         614 return(1);
713             }
714              
715              
716              
717             =head2 BLOBParams FIELD_NAME FIELD_TYPE
718              
719             Returns a hash ref for the bind_param call to identify BLOB types used by
720             the current database for a particular column type.
721              
722             =cut
723              
724             sub BLOBParams {
725 0     0 1 0 my $self = shift;
726             # Don't assign to key 'value' as it is defined later.
727 0         0 return ( {} );
728             }
729              
730              
731              
732             =head2 DatabaseVersion [Short => 1]
733              
734             Returns the database's version.
735              
736             If argument C is true returns short variant, in other
737             case returns whatever database handle/driver returns. By default
738             returns short version, e.g. '4.1.23' or '8.0-rc4'.
739              
740             Returns empty string on error or if database couldn't return version.
741              
742             The base implementation uses a C
743              
744             =cut
745              
746             sub DatabaseVersion {
747 0     0 1 0 my $self = shift;
748 0         0 my %args = ( Short => 1, @_ );
749              
750 0 0       0 unless ( defined $self->{'database_version'} ) {
751              
752             # turn off error handling, store old values to restore later
753 0         0 my $re = $self->RaiseError;
754 0         0 $self->RaiseError(0);
755 0         0 my $pe = $self->PrintError;
756 0         0 $self->PrintError(0);
757              
758 0         0 my $statement = "SELECT VERSION()";
759 0         0 my $sth = $self->SimpleQuery($statement);
760              
761 0         0 my $ver = '';
762 0 0 0     0 $ver = ( $sth->fetchrow_arrayref->[0] || '' ) if $sth;
763 0         0 $ver =~ /(\d+(?:\.\d+)*(?:-[a-z0-9]+)?)/i;
764 0         0 $self->{'database_version'} = $ver;
765 0   0     0 $self->{'database_version_short'} = $1 || $ver;
766              
767 0         0 $self->RaiseError($re);
768 0         0 $self->PrintError($pe);
769             }
770              
771 0 0       0 return $self->{'database_version_short'} if $args{'Short'};
772 0         0 return $self->{'database_version'};
773             }
774              
775             =head2 CaseSensitive
776              
777             Returns 1 if the current database's searches are case sensitive by default
778             Returns undef otherwise
779              
780             =cut
781              
782             sub CaseSensitive {
783 0     0 1 0 my $self = shift;
784 0         0 return(1);
785             }
786              
787             =head2 QuoteTableNames
788              
789             Returns 1 if table names will be quoted in queries, otherwise 0
790              
791             =cut
792              
793             sub QuoteTableNames {
794 328     328 1 1398 return shift->{'QuoteTableNames'}
795             }
796              
797              
798              
799              
800              
801             =head2 _MakeClauseCaseInsensitive FIELD OPERATOR VALUE
802              
803             Takes a field, operator and value. performs the magic necessary to make
804             your database treat this clause as case insensitive.
805              
806             Returns a FIELD OPERATOR VALUE triple.
807              
808             =cut
809              
810             our $RE_CASE_INSENSITIVE_CHARS = qr/[-'"\d: ]/;
811              
812             sub _MakeClauseCaseInsensitive {
813 43     43   77 my $self = shift;
814 43         82 my $field = shift;
815 43         108 my $operator = shift;
816 43         81 my $value = shift;
817              
818             # don't downcase integer values and things that looks like dates
819 43 100       859 if ($value !~ /^$RE_CASE_INSENSITIVE_CHARS+$/o) {
820 30         95 $field = "lower($field)";
821 30         78 $value = lc($value);
822             }
823 43         218 return ($field, $operator, $value,undef);
824             }
825              
826             =head2 Transactions
827              
828             L emulates nested transactions,
829             by keeping a transaction stack depth.
830              
831             B In nested transactions you shouldn't mix rollbacks and commits,
832             because only last action really do commit/rollback. For example next code
833             would produce desired results:
834              
835             $handle->BeginTransaction;
836             $handle->BeginTransaction;
837             ...
838             $handle->Rollback;
839             $handle->BeginTransaction;
840             ...
841             $handle->Commit;
842             $handle->Commit;
843              
844             Only last action(Commit in example) finilize transaction in DB.
845              
846             =head3 BeginTransaction
847              
848             Tells DBIx::SearchBuilder to begin a new SQL transaction.
849             This will temporarily suspend Autocommit mode.
850              
851             =cut
852              
853             sub BeginTransaction {
854 10     10 1 2467 my $self = shift;
855              
856 10         28 my $depth = $self->TransactionDepth;
857 10 100       27 return unless defined $depth;
858              
859 9         32 $self->TransactionDepth(++$depth);
860 9 100       36 return 1 if $depth > 1;
861              
862 6         14 return $self->dbh->begin_work;
863             }
864              
865             =head3 EndTransaction [Action => 'commit'] [Force => 0]
866              
867             Tells to end the current transaction. Takes C argument
868             that could be C or C, the default value
869             is C.
870              
871             If C argument is true then all nested transactions
872             would be committed or rolled back.
873              
874             If there is no transaction in progress then method throw
875             warning unless action is forced.
876              
877             Method returns true on success or false if an error occurred.
878              
879             =cut
880              
881             sub EndTransaction {
882 20     20 1 27 my $self = shift;
883 20         75 my %args = ( Action => 'commit', Force => 0, @_ );
884 20 100       62 my $action = lc $args{'Action'} eq 'commit'? 'commit': 'rollback';
885              
886 20   100     41 my $depth = $self->TransactionDepth || 0;
887 20 100       37 unless ( $depth ) {
888 11 100       23 unless( $args{'Force'} ) {
889 4         677 Carp::cluck( "Attempted to $action a transaction with none in progress" );
890 4         39 return 0;
891             }
892 7         24 return 1;
893             } else {
894 9         16 $depth--;
895             }
896 9 50       25 $depth = 0 if $args{'Force'};
897              
898 9         24 $self->TransactionDepth( $depth );
899              
900 9         20 my $dbh = $self->dbh;
901 9         35 $TRANSROLLBACK{ $dbh }{ $action }++;
902 9 100       35 if ( $TRANSROLLBACK{ $dbh }{ $action eq 'commit'? 'rollback' : 'commit' } ) {
    100          
903 2         36 warn "Rollback and commit are mixed while escaping nested transaction";
904             }
905 9 100       87 return 1 if $depth;
906              
907 6         18 delete $TRANSROLLBACK{ $dbh };
908              
909 6 100       15 if ($action eq 'commit') {
910 4         16271 return $dbh->commit;
911             }
912             else {
913 2 50       27 DBIx::SearchBuilder::Record::Cachable->FlushCache
914             if DBIx::SearchBuilder::Record::Cachable->can('FlushCache');
915 2         22 return $dbh->rollback;
916             }
917             }
918              
919             =head3 Commit [FORCE]
920              
921             Tells to commit the current SQL transaction.
922              
923             Method uses C method, read its
924             L.
925              
926             =cut
927              
928             sub Commit {
929 10     10 1 557 my $self = shift;
930 10         31 $self->EndTransaction( Action => 'commit', Force => shift );
931             }
932              
933              
934             =head3 Rollback [FORCE]
935              
936             Tells to abort the current SQL transaction.
937              
938             Method uses C method, read its
939             L.
940              
941             =cut
942              
943             sub Rollback {
944 8     8 1 16 my $self = shift;
945 8         38 $self->EndTransaction( Action => 'rollback', Force => shift );
946             }
947              
948              
949             =head3 ForceRollback
950              
951             Force the handle to rollback.
952             Whether or not we're deep in nested transactions.
953              
954             =cut
955              
956             sub ForceRollback {
957 1     1 1 2 my $self = shift;
958 1         5 $self->Rollback(1);
959             }
960              
961              
962             =head3 TransactionDepth
963              
964             Returns the current depth of the nested transaction stack.
965             Returns C if there is no connection to database.
966              
967             =cut
968              
969             sub TransactionDepth {
970 61     61 1 2286 my $self = shift;
971              
972 61         129 my $dbh = $self->dbh;
973 61 100 66     370 return undef unless $dbh && $dbh->ping;
974              
975 51 100       1848 if ( @_ ) {
976 18         30 my $depth = shift;
977 18 100       40 if ( $depth ) {
978 12         35 $TRANSDEPTH{ $dbh } = $depth;
979             } else {
980 6         18 delete $TRANSDEPTH{ $dbh };
981             }
982             }
983 51   100     244 return $TRANSDEPTH{ $dbh } || 0;
984             }
985              
986              
987             =head2 ApplyLimits STATEMENTREF ROWS_PER_PAGE FIRST_ROW
988              
989             takes an SQL SELECT statement and massages it to return ROWS_PER_PAGE starting with FIRST_ROW;
990              
991             =cut
992              
993             sub ApplyLimits {
994 202     202 1 362 my $self = shift;
995 202         333 my $statementref = shift;
996 202         377 my $per_page = shift;
997 202         303 my $first = shift;
998 202         308 my $sb = shift;
999              
1000 202         394 my $limit_clause = '';
1001              
1002 202 100       519 if ( $per_page) {
1003 67         130 $limit_clause = " LIMIT ";
1004 67 100       193 if ( $sb->{_bind_values} ) {
1005 4   66     6 push @{$sb->{_bind_values}}, $first || (), $per_page;
  4         18  
1006 4 100       28 $first = '?' if $first;
1007 4         8 $per_page = '?';
1008             }
1009              
1010 67 100       161 if ( $first ) {
1011 36         125 $limit_clause .= $first . ", ";
1012             }
1013 67         141 $limit_clause .= $per_page;
1014             }
1015              
1016 202         602 $$statementref .= $limit_clause;
1017             }
1018              
1019              
1020              
1021              
1022              
1023             =head2 Join { Paramhash }
1024              
1025             Takes a paramhash of everything Searchbuildler::Record does
1026             plus a parameter called 'SearchBuilder' that contains a ref
1027             to a SearchBuilder object'.
1028              
1029             This performs the join.
1030              
1031              
1032             =cut
1033              
1034              
1035             sub Join {
1036              
1037 104     104 1 216 my $self = shift;
1038 104         823 my %args = (
1039             SearchBuilder => undef,
1040             TYPE => 'normal',
1041             ALIAS1 => 'main',
1042             FIELD1 => undef,
1043             TABLE2 => undef,
1044             COLLECTION2 => undef,
1045             FIELD2 => undef,
1046             ALIAS2 => undef,
1047             EXPRESSION => undef,
1048             @_
1049             );
1050              
1051              
1052 104         202 my $alias;
1053              
1054             #If we're handed in an ALIAS2, we need to go remove it from the Aliases array.
1055             # Basically, if anyone generates an alias and then tries to use it in a join later, we want to be smart about
1056             # creating joins, so we need to go rip it out of the old aliases table and drop it in as an explicit join
1057 104 100       380 if ( $args{'ALIAS2'} ) {
    50          
1058              
1059             # this code is slow and wasteful, but it's clear.
1060 4         11 my @aliases = @{ $args{'SearchBuilder'}->{'aliases'} };
  4         17  
1061 4         10 my @new_aliases;
1062 4         14 foreach my $old_alias (@aliases) {
1063 4 50       174 if ( $old_alias =~ /^(.*?) (\Q$args{'ALIAS2'}\E)$/ ) {
1064 4         22 $args{'TABLE2'} = $1;
1065 4         14 $alias = $2;
1066 4 50       17 $args{'TABLE2'} = $self->DequoteName($args{'TABLE2'}) if $self->QuoteTableNames;
1067             }
1068             else {
1069 0         0 push @new_aliases, $old_alias;
1070             }
1071             }
1072              
1073             # If we found an alias, great. let's just pull out the table and alias for the other item
1074 4 50       15 unless ($alias) {
1075              
1076             # if we can't do that, can we reverse the join and have it work?
1077 0         0 my $a1 = $args{'ALIAS1'};
1078 0         0 my $f1 = $args{'FIELD1'};
1079 0         0 $args{'ALIAS1'} = $args{'ALIAS2'};
1080 0         0 $args{'FIELD1'} = $args{'FIELD2'};
1081 0         0 $args{'ALIAS2'} = $a1;
1082 0         0 $args{'FIELD2'} = $f1;
1083              
1084 0         0 @aliases = @{ $args{'SearchBuilder'}->{'aliases'} };
  0         0  
1085 0         0 @new_aliases = ();
1086 0         0 foreach my $old_alias (@aliases) {
1087 0 0       0 if ( $old_alias =~ /^(.*?) ($args{'ALIAS2'})$/ ) {
1088 0         0 $args{'TABLE2'} = $1;
1089 0         0 $alias = $2;
1090 0 0       0 $args{'TABLE2'} = $self->DequoteName($args{'TABLE2'}) if $self->QuoteTableNames;
1091             }
1092             else {
1093 0         0 push @new_aliases, $old_alias;
1094             }
1095             }
1096              
1097             } else {
1098             # we found alias, so NewAlias should take care of distinctness
1099 4 50       20 $args{'DISTINCT'} = 1 unless exists $args{'DISTINCT'};
1100             }
1101              
1102 4 50       22 unless ( $alias ) {
1103             # XXX: this situation is really bug in the caller!!!
1104 0         0 return ( $self->_NormalJoin(%args) );
1105             }
1106 4         19 $args{'SearchBuilder'}->{'aliases'} = \@new_aliases;
1107             } elsif ( $args{'COLLECTION2'} ) {
1108             # We're joining to a pre-limited collection. We need to take
1109             # all clauses in the other collection, munge 'main.' to a new
1110             # alias, apply them locally, then proceed as usual.
1111 0         0 my $collection = delete $args{'COLLECTION2'};
1112 0         0 $alias = $args{ALIAS2} = $args{'SearchBuilder'}->_GetAlias( $collection->Table );
1113 0         0 $args{TABLE2} = $collection->Table;
1114              
1115 0         0 eval {$collection->_ProcessRestrictions}; # RT hate
  0         0  
1116              
1117             # Move over unused aliases
1118 0         0 push @{$args{SearchBuilder}{aliases}}, @{$collection->{aliases}};
  0         0  
  0         0  
1119              
1120             # Move over joins, as well
1121 0         0 for my $join (sort keys %{$collection->{left_joins}}) {
  0         0  
1122 0         0 my %alias = %{$collection->{left_joins}{$join}};
  0         0  
1123 0 0       0 $alias{depends_on} = $alias if $alias{depends_on} eq "main";
1124             $alias{criteria} = $self->_RenameRestriction(
1125             RESTRICTIONS => $alias{criteria},
1126 0         0 NEW => $alias
1127             );
1128 0         0 $args{SearchBuilder}{left_joins}{$join} = \%alias;
1129             }
1130              
1131             my $restrictions = $self->_RenameRestriction(
1132             RESTRICTIONS => $collection->{restrictions},
1133 0         0 NEW => $alias
1134             );
1135 0         0 $args{SearchBuilder}{restrictions}{$_} = $restrictions->{$_} for keys %{$restrictions};
  0         0  
1136             } else {
1137 100         375 $alias = $args{'SearchBuilder'}->_GetAlias( $args{'TABLE2'} );
1138             }
1139 104 50       382 $args{TABLE2} = $self->QuoteName($args{TABLE2}) if $self->QuoteTableNames;
1140              
1141 104   50     743 my $meta = $args{'SearchBuilder'}->{'left_joins'}{"$alias"} ||= {};
1142 104 100       614 if ( $args{'TYPE'} =~ /LEFT/i ) {
1143 39         156 $meta->{'alias_string'} = " LEFT JOIN " . $args{'TABLE2'} . " $alias ";
1144 39         112 $meta->{'type'} = 'LEFT';
1145             }
1146             else {
1147 65         244 $meta->{'alias_string'} = " JOIN " . $args{'TABLE2'} . " $alias ";
1148 65         156 $meta->{'type'} = 'NORMAL';
1149             }
1150 104         295 $meta->{'depends_on'} = $args{'ALIAS1'};
1151              
1152 104   33     522 my $criterion = $args{'EXPRESSION'} || $args{'ALIAS1'}.".".$args{'FIELD1'};
1153 104         851 $meta->{'criteria'}{'base_criterion'} =
1154             [ { field => "$alias.$args{'FIELD2'}", op => '=', value => $criterion } ];
1155              
1156 104 100 100     557 if ( $args{'DISTINCT'} && !defined $args{'SearchBuilder'}{'joins_are_distinct'} ) {
    100          
1157 1         3 $args{SearchBuilder}{joins_are_distinct} = 1;
1158             } elsif ( !$args{'DISTINCT'} ) {
1159 98         236 $args{SearchBuilder}{joins_are_distinct} = 0;
1160             }
1161              
1162 104         701 return ($alias);
1163             }
1164              
1165             sub _RenameRestriction {
1166 0     0   0 my $self = shift;
1167 0         0 my %args = (
1168             RESTRICTIONS => undef,
1169             OLD => "main",
1170             NEW => undef,
1171             @_,
1172             );
1173              
1174 0         0 my %return;
1175 0         0 for my $key ( keys %{$args{RESTRICTIONS}} ) {
  0         0  
1176 0         0 my $newkey = $key;
1177 0         0 $newkey =~ s/^\Q$args{OLD}\E\./$args{NEW}./;
1178 0         0 my @parts;
1179 0         0 for my $part ( @{ $args{RESTRICTIONS}{$key} } ) {
  0         0  
1180 0 0       0 if ( ref $part ) {
1181 0         0 my %part = %{$part};
  0         0  
1182 0         0 $part{field} =~ s/^\Q$args{OLD}\E\./$args{NEW}./;
1183 0         0 $part{value} =~ s/^\Q$args{OLD}\E\./$args{NEW}./;
1184 0         0 push @parts, \%part;
1185             } else {
1186 0         0 push @parts, $part;
1187             }
1188             }
1189 0         0 $return{$newkey} = \@parts;
1190             }
1191 0         0 return \%return;
1192             }
1193              
1194             sub _NormalJoin {
1195              
1196 0     0   0 my $self = shift;
1197 0         0 my %args = (
1198             SearchBuilder => undef,
1199             TYPE => 'normal',
1200             FIELD1 => undef,
1201             ALIAS1 => undef,
1202             TABLE2 => undef,
1203             FIELD2 => undef,
1204             ALIAS2 => undef,
1205             @_
1206             );
1207              
1208 0         0 my $sb = $args{'SearchBuilder'};
1209              
1210 0 0       0 if ( $args{'TYPE'} =~ /LEFT/i ) {
1211 0         0 my $alias = $sb->_GetAlias( $args{'TABLE2'} );
1212 0   0     0 my $meta = $sb->{'left_joins'}{"$alias"} ||= {};
1213 0 0       0 $args{TABLE2} = $self->QuoteName($args{TABLE2}) if $self->QuoteTableNames;
1214 0         0 $meta->{'alias_string'} = " LEFT JOIN $args{'TABLE2'} $alias ";
1215 0         0 $meta->{'depends_on'} = $args{'ALIAS1'};
1216 0         0 $meta->{'type'} = 'LEFT';
1217 0         0 $meta->{'criteria'}{'base_criterion'} = [ {
1218             field => "$args{'ALIAS1'}.$args{'FIELD1'}",
1219             op => '=',
1220             value => "$alias.$args{'FIELD2'}",
1221             } ];
1222              
1223 0         0 return ($alias);
1224             }
1225             else {
1226             $sb->DBIx::SearchBuilder::Limit(
1227             ENTRYAGGREGATOR => 'AND',
1228             QUOTEVALUE => 0,
1229             ALIAS => $args{'ALIAS1'},
1230             FIELD => $args{'FIELD1'},
1231 0         0 VALUE => $args{'ALIAS2'} . "." . $args{'FIELD2'},
1232             @_
1233             );
1234             }
1235             }
1236              
1237             # this code is all hacky and evil. but people desperately want _something_ and I'm
1238             # super tired. refactoring gratefully appreciated.
1239              
1240             sub _BuildJoins {
1241 289     289   515 my $self = shift;
1242 289         454 my $sb = shift;
1243              
1244 289         990 $self->OptimizeJoins( SearchBuilder => $sb );
1245 289 50       1278 my $table = $self->{'QuoteTableNames'} ? $self->QuoteName($sb->Table) : $sb->Table;
1246              
1247 289         638 my $join_clause = join " CROSS JOIN ", ("$table main"), @{ $sb->{'aliases'} };
  289         979  
1248 289         546 my %processed = map { /^\S+\s+(\S+)$/; $1 => 1 } @{ $sb->{'aliases'} };
  2         10  
  2         10  
  289         691  
1249 289         628 $processed{'main'} = 1;
1250              
1251             # get a @list of joins that have not been processed yet, but depend on processed join
1252 289         578 my $joins = $sb->{'left_joins'};
1253 289   100     1819 while ( my @list =
1254             grep !$processed{ $_ }
1255             && (!$joins->{ $_ }{'depends_on'} || $processed{ $joins->{ $_ }{'depends_on'} }),
1256             sort keys %$joins
1257             ) {
1258 157         424 foreach my $join ( @list ) {
1259 157         313 $processed{ $join }++;
1260              
1261 157         300 my $meta = $joins->{ $join };
1262 157   100     560 my $aggregator = $meta->{'entry_aggregator'} || 'AND';
1263              
1264 157         464 $join_clause .= $meta->{'alias_string'} . " ON ";
1265             my @tmp = map {
1266             ref($_)?
1267 664 100       1772 $_->{'field'} .' '. $_->{'op'} .' '. $_->{'value'}:
1268             $_
1269             }
1270 157         600 map { ('(', @$_, ')', $aggregator) } sorted_values($meta->{'criteria'});
  166         549  
1271 157         410 pop @tmp;
1272 157         1241 $join_clause .= join ' ', @tmp;
1273             }
1274             }
1275              
1276             # here we could check if there is recursion in joins by checking that all joins
1277             # are processed
1278 289 50       986 if ( my @not_processed = grep !$processed{ $_ }, keys %$joins ) {
1279 0         0 die "Unsatisfied dependency chain in joins @not_processed";
1280             }
1281 289         1242 return $join_clause;
1282             }
1283              
1284             sub OptimizeJoins {
1285 289     289 0 515 my $self = shift;
1286 289         1047 my %args = (SearchBuilder => undef, @_);
1287 289         691 my $joins = $args{'SearchBuilder'}->{'left_joins'};
1288              
1289 289         470 my %processed = map { /^\S+\s+(\S+)$/; $1 => 1 } @{ $args{'SearchBuilder'}->{'aliases'} };
  2         15  
  2         15  
  289         852  
1290 289         1478 $processed{ $_ }++ foreach grep $joins->{ $_ }{'type'} ne 'LEFT', keys %$joins;
1291 289         668 $processed{'main'}++;
1292              
1293 289         520 my @ordered;
1294             # get a @list of joins that have not been processed yet, but depend on processed join
1295             # if we are talking about forest then we'll get the second level of the forest,
1296             # but we should process nodes on this level at the end, so we build FILO ordered list.
1297             # finally we'll get ordered list with leafes in the beginning and top most nodes at
1298             # the end.
1299 289   100     1671 while ( my @list = grep !$processed{ $_ }
1300             && $processed{ $joins->{ $_ }{'depends_on'} }, sort keys %$joins )
1301             {
1302 51         162 unshift @ordered, @list;
1303 51         384 $processed{ $_ }++ foreach @list;
1304             }
1305              
1306 289         982 foreach my $join ( @ordered ) {
1307 51 100       170 next if $self->MayBeNull( SearchBuilder => $args{'SearchBuilder'}, ALIAS => $join );
1308              
1309 3         38 $joins->{ $join }{'alias_string'} =~ s/^\s*LEFT\s+/ /;
1310 3         13 $joins->{ $join }{'type'} = 'NORMAL';
1311             }
1312              
1313             # here we could check if there is recursion in joins by checking that all joins
1314             # are processed
1315              
1316             }
1317              
1318             =head2 MayBeNull
1319              
1320             Takes a C and C in a hash and resturns
1321             true if restrictions of the query allow NULLs in a table joined with
1322             the ALIAS, otherwise returns false value which means that you can
1323             use normal join instead of left for the aliased table.
1324              
1325             Works only for queries have been built with L and
1326             L methods, for other cases return true value to
1327             avoid fault optimizations.
1328              
1329             =cut
1330              
1331             sub MayBeNull {
1332 51     51 1 104 my $self = shift;
1333 51         224 my %args = (SearchBuilder => undef, ALIAS => undef, @_);
1334             # if we have at least one subclause that is not generic then we should get out
1335             # of here as we can't parse subclauses
1336 51 50       110 return 1 if grep $_ ne 'generic_restrictions', keys %{ $args{'SearchBuilder'}->{'subclauses'} };
  51         220  
1337              
1338             # build full list of generic conditions
1339 51         94 my @conditions;
1340 51         286 foreach ( grep @$_, sorted_values($args{'SearchBuilder'}->{'restrictions'}) ) {
1341 10 50       31 push @conditions, 'AND' if @conditions;
1342 10         36 push @conditions, '(', @$_, ')';
1343             }
1344              
1345             # find tables that depends on this alias and add their join conditions
1346 51         197 foreach my $join ( sorted_values($args{'SearchBuilder'}->{'left_joins'}) ) {
1347             # left joins on the left side so later we'll get 1 AND x expression
1348             # which equal to x, so we just skip it
1349 53 100       176 next if $join->{'type'} eq 'LEFT';
1350 1 50       8 next unless $join->{'depends_on'} eq $args{'ALIAS'};
1351              
1352 1         5 my @tmp = map { ('(', @$_, ')', $join->{'entry_aggregator'}) } sorted_values($join->{'criteria'});
  1         6  
1353 1         5 pop @tmp;
1354              
1355 1         7 @conditions = ('(', @conditions, ')', 'AND', '(', @tmp ,')');
1356              
1357             }
1358 51 100       347 return 1 unless @conditions;
1359              
1360             # replace conditions with boolean result: 1 - allows nulls, 0 - not
1361             # all restrictions on that don't act on required alias allow nulls
1362             # otherwise only IS NULL operator
1363 10         28 foreach ( splice @conditions ) {
1364 46 50       220 unless ( ref $_ ) {
    100          
    100          
    100          
1365 33         73 push @conditions, $_;
1366 0         0 } elsif ( rindex( $_->{'field'}, "$args{'ALIAS'}.", 0 ) == 0 ) {
1367             # field is alias.xxx op ... and only IS op allows NULLs
1368 9         31 push @conditions, lc $_->{op} eq 'is';
1369 0 50       0 } elsif ( $_->{'value'} && rindex( $_->{'value'}, "$args{'ALIAS'}.", 0 ) == 0 ) {
1370             # value is alias.xxx so it can not be IS op
1371 1         3 push @conditions, 0;
1372 0         0 } elsif ( $_->{'field'} =~ /^(?i:lower)\(\s*\Q$args{'ALIAS'}\./ ) {
1373             # handle 'LOWER(alias.xxx) OP VALUE' we use for case insensetive
1374 0         0 push @conditions, lc $_->{op} eq 'is';
1375             } else {
1376 3         9 push @conditions, 1;
1377             }
1378             }
1379              
1380             # resturns index of closing paren by index of openning paren
1381             my $closing_paren = sub {
1382 0     0   0 my $i = shift;
1383 0         0 my $count = 0;
1384 0         0 for ( ; $i < @conditions; $i++ ) {
1385 0 0       0 if ( $conditions[$i] eq '(' ) {
    0          
1386 0         0 $count++;
1387             }
1388             elsif ( $conditions[$i] eq ')' ) {
1389 0         0 $count--;
1390             }
1391 0 0       0 return $i unless $count;
1392             }
1393 0         0 die "lost in parens";
1394 10         65 };
1395              
1396             # solve boolean expression we have, an answer is our result
1397 10         21 my $parens_count = 0;
1398 10         23 my @tmp = ();
1399 10         35 while ( defined ( my $e = shift @conditions ) ) {
1400             #print "@tmp >>>$e<<< @conditions\n";
1401 48 100 100     219 return $e if !@conditions && !@tmp;
1402              
1403 38 50       129 unless ( $e ) {
    100          
    100          
    100          
1404 3 100       10 if ( $conditions[0] eq ')' ) {
1405 1         2 push @tmp, $e;
1406 1         4 next;
1407             }
1408              
1409 2         8 my $aggreg = uc shift @conditions;
1410 2 50       11 if ( $aggreg eq 'OR' ) {
    0          
1411             # 0 OR x == x
1412 2         36 next;
1413             } elsif ( $aggreg eq 'AND' ) {
1414             # 0 AND x == 0
1415 0         0 my $close_p = $closing_paren->(0);
1416 0         0 splice @conditions, 0, $close_p + 1, (0);
1417             } else {
1418 0         0 die "unknown aggregator: @tmp $e >>>$aggreg<<< @conditions";
1419             }
1420 0         0 } elsif ( $e eq '1' ) {
1421 6 100       17 if ( $conditions[0] eq ')' ) {
1422 5         11 push @tmp, $e;
1423 5         16 next;
1424             }
1425              
1426 1         5 my $aggreg = uc shift @conditions;
1427 1 50       7 if ( $aggreg eq 'OR' ) {
    50          
1428             # 1 OR x == 1
1429 0         0 my $close_p = $closing_paren->(0);
1430 0         0 splice @conditions, 0, $close_p + 1, (1);
1431             } elsif ( $aggreg eq 'AND' ) {
1432             # 1 AND x == x
1433 1         4 next;
1434             } else {
1435 0         0 die "unknown aggregator: @tmp $e >>>$aggreg<<< @conditions";
1436             }
1437 0         0 } elsif ( $e eq '(' ) {
1438 23 100       48 if ( $conditions[1] eq ')' ) {
1439 15         53 splice @conditions, 1, 1;
1440             } else {
1441 8         13 $parens_count++;
1442 8         27 push @tmp, $e;
1443             }
1444 0         0 } elsif ( $e eq ')' ) {
1445 6 50       34 die "extra closing paren: @tmp >>>$e<<< @conditions"
1446             if --$parens_count < 0;
1447              
1448 6         19 unshift @conditions, @tmp, $e;
1449 6         22 @tmp = ();
1450             } else {
1451 0         0 die "lost: @tmp >>>$e<<< @conditions";
1452             }
1453             }
1454 0         0 return 1;
1455             }
1456              
1457             =head2 DistinctQuery STATEMENTREF
1458              
1459             takes an incomplete SQL SELECT statement and massages it to return a DISTINCT result set.
1460              
1461              
1462             =cut
1463              
1464             sub DistinctQuery {
1465 64     64 1 127 my $self = shift;
1466 64         102 my $statementref = shift;
1467 64         128 my $sb = shift;
1468 64         198 my %args = (
1469             Wrap => 0,
1470             @_
1471             );
1472              
1473 64         170 my $QueryHint = $sb->QueryHint;
1474 64 50       161 $QueryHint = $QueryHint ? " /* $QueryHint */ " : " ";
1475              
1476             # Prepend select query for DBs which allow DISTINCT on all column types.
1477 64         273 $$statementref = "SELECT" . $QueryHint . "DISTINCT main.* FROM $$statementref";
1478 64         180 $$statementref .= $sb->_GroupClause;
1479 64 100       197 if ( $args{'Wrap'} ) {
1480 25         64 $$statementref = "SELECT * FROM ($$statementref) main";
1481             }
1482 64         164 $$statementref .= $sb->_OrderClause;
1483             }
1484              
1485             =head2 DistinctQueryAndCount STATEMENTREF
1486              
1487             takes an incomplete SQL SELECT statement and massages it to return a
1488             DISTINCT result set and the total count of potential records.
1489              
1490             =cut
1491              
1492             sub DistinctQueryAndCount {
1493 41     41 1 101 my $self = shift;
1494 41         67 my $statementref = shift;
1495 41         85 my $sb = shift;
1496              
1497             # order by clause should be on outer most query
1498             # SQL standard: ORDER BY command cannot be used in a Subquery
1499             # mariadb explanation: https://mariadb.com/kb/en/why-is-order-by-in-a-from-subquery-ignored/
1500             # pg: actually keeps order of a subquery as long as some conditions in outer query are met, but
1501             # it's just a coincidence, not a feature
1502             # SQL server: https://learn.microsoft.com/en-us/sql/t-sql/queries/select-order-by-clause-transact-sql?view=sql-server-ver16
1503             # The ORDER BY clause is not valid in views, ... and *subqueries*, unless ...
1504              
1505 41         121 my $order = $sb->_OrderClause;
1506 41         206 my $wrap = $order !~ /(?
1507              
1508 41         233 $self->DistinctQuery($statementref, $sb, Wrap => $wrap);
1509              
1510             # DistinctQuery already has an outer SELECT, which we can reuse
1511 41         378 $$statementref =~ s!(?= FROM)!, COUNT(main.id) OVER() AS search_builder_count_all!;
1512             }
1513              
1514              
1515              
1516             =head2 DistinctCount STATEMENTREF
1517              
1518             takes an incomplete SQL SELECT statement and massages it to return a DISTINCT result set.
1519              
1520              
1521             =cut
1522              
1523             sub DistinctCount {
1524 0     0 1 0 my $self = shift;
1525 0         0 my $statementref = shift;
1526 0         0 my $sb = shift;
1527              
1528 0         0 my $QueryHint = $sb->QueryHint;
1529 0 0       0 $QueryHint = $QueryHint ? " /* $QueryHint */ " : " ";
1530              
1531             # Prepend select query for DBs which allow DISTINCT on all column types.
1532 0         0 $$statementref = "SELECT" . $QueryHint . "COUNT(DISTINCT main.id) FROM $$statementref";
1533              
1534             }
1535              
1536             sub Fields {
1537 0     0 0 0 my $self = shift;
1538 0         0 my $table = lc shift;
1539              
1540 0 0       0 unless ( $FIELDS_IN_TABLE{$table} ) {
1541 0         0 $FIELDS_IN_TABLE{ $table } = [];
1542 0 0       0 my $sth = $self->dbh->column_info( undef, '', $table, '%' )
1543             or return ();
1544 0         0 my $info = $sth->fetchall_arrayref({});
1545 0         0 foreach my $e ( @$info ) {
1546 0         0 push @{ $FIELDS_IN_TABLE{ $table } }, $e->{'COLUMN_NAME'};
  0         0  
1547             }
1548             }
1549              
1550 0         0 return @{ $FIELDS_IN_TABLE{ $table } };
  0         0  
1551             }
1552              
1553              
1554             =head2 Log MESSAGE
1555              
1556             Takes a single argument, a message to log.
1557              
1558             Currently prints that message to STDERR
1559              
1560             =cut
1561              
1562             sub Log {
1563 0     0 1 0 my $self = shift;
1564 0         0 my $msg = shift;
1565 0         0 warn $msg."\n";
1566             }
1567              
1568             =head2 SimpleDateTimeFunctions
1569              
1570             See L for details on supported functions.
1571             This method is for implementers of custom DB connectors.
1572              
1573             Returns hash reference with (function name, sql template) pairs.
1574              
1575             =cut
1576              
1577             sub SimpleDateTimeFunctions {
1578 1     1 1 3 my $self = shift;
1579             return {
1580 1         31 datetime => 'SUBSTR(?, 1, 19)',
1581             time => 'SUBSTR(?, 12, 8)',
1582              
1583             hourly => 'SUBSTR(?, 1, 13)',
1584             hour => 'SUBSTR(?, 12, 2 )',
1585              
1586             date => 'SUBSTR(?, 1, 10)',
1587             daily => 'SUBSTR(?, 1, 10)',
1588              
1589             day => 'SUBSTR(?, 9, 2 )',
1590             dayofmonth => 'SUBSTR(?, 9, 2 )',
1591              
1592             monthly => 'SUBSTR(?, 1, 7 )',
1593             month => 'SUBSTR(?, 6, 2 )',
1594              
1595             annually => 'SUBSTR(?, 1, 4 )',
1596             year => 'SUBSTR(?, 1, 4 )',
1597             };
1598             }
1599              
1600             =head2 DateTimeFunction
1601              
1602             Takes named arguments:
1603              
1604             =over 4
1605              
1606             =item * Field - SQL expression date/time function should be applied
1607             to. Note that this argument is used as is without any kind of quoting.
1608              
1609             =item * Type - name of the function, see supported values below.
1610              
1611             =item * Timezone - optional hash reference with From and To values,
1612             see L for details.
1613              
1614             =back
1615              
1616             Returns SQL statement. Returns NULL if function is not supported.
1617              
1618             =head3 Supported functions
1619              
1620             Type value in L is case insesitive. Spaces,
1621             underscores and dashes are ignored. So 'date time', 'DateTime'
1622             and 'date_time' are all synonyms. The following functions are
1623             supported:
1624              
1625             =over 4
1626              
1627             =item * date time - as is, no conversion, except applying timezone
1628             conversion if it's provided.
1629              
1630             =item * time - time only
1631              
1632             =item * hourly - datetime prefix up to the hours, e.g. '2010-03-25 16'
1633              
1634             =item * hour - hour, 0 - 23
1635              
1636             =item * date - date only
1637              
1638             =item * daily - synonym for date
1639              
1640             =item * day of week - 0 - 6, 0 - Sunday
1641              
1642             =item * day - day of month, 1 - 31
1643              
1644             =item * day of month - synonym for day
1645              
1646             =item * day of year - 1 - 366, support is database dependent
1647              
1648             =item * month - 1 - 12
1649              
1650             =item * monthly - year and month prefix, e.g. '2010-11'
1651              
1652             =item * year - e.g. '2023'
1653              
1654             =item * annually - synonym for year
1655              
1656             =item * week of year - 0-53, presence of zero week, 1st week meaning
1657             and whether week starts on Monday or Sunday heavily depends on database.
1658              
1659             =back
1660              
1661             =cut
1662              
1663             sub DateTimeFunction {
1664 20     20 1 683 my $self = shift;
1665 20         73 my %args = (
1666             Field => undef,
1667             Type => '',
1668             Timezone => undef,
1669             @_
1670             );
1671              
1672 20   50     82 my $res = $args{'Field'} || '?';
1673 20 50       49 if ( $args{'Timezone'} ) {
1674             $res = $self->ConvertTimezoneFunction(
1675 0         0 %{ $args{'Timezone'} },
  0         0  
1676             Field => $res,
1677             );
1678             }
1679              
1680 20         50 my $norm_type = lc $args{'Type'};
1681 20         88 $norm_type =~ s/[ _-]//g;
1682 20 100       70 if ( my $template = $self->SimpleDateTimeFunctions->{ $norm_type } ) {
1683 18         63 $template =~ s/\?/$res/;
1684 18         35 $res = $template;
1685             }
1686             else {
1687 2         18 return 'NULL';
1688             }
1689 18         54 return $res;
1690             }
1691              
1692             =head2 ConvertTimezoneFunction
1693              
1694             Generates a function applied to Field argument that converts timezone.
1695             By default converts from UTC. Examples:
1696              
1697             # UTC => Moscow
1698             $handle->ConvertTimezoneFunction( Field => '?', To => 'Europe/Moscow');
1699              
1700             If there is problem with arguments or timezones are equal
1701             then Field returned without any function applied. Field argument
1702             is not escaped in any way, it's your job.
1703              
1704             Implementation is very database specific. To be portable convert
1705             from UTC or to UTC. Some databases have internal storage for
1706             information about timezones that should be kept up to date.
1707             Read documentation for your DB.
1708              
1709             =cut
1710              
1711             sub ConvertTimezoneFunction {
1712 0     0 1 0 my $self = shift;
1713 0         0 my %args = (
1714             From => 'UTC',
1715             To => undef,
1716             Field => '',
1717             @_
1718             );
1719 0         0 return $args{'Field'};
1720             }
1721              
1722             =head2 DateTimeIntervalFunction
1723              
1724             Generates a function to calculate interval in seconds between two
1725             dates. Takes From and To arguments which can be either scalar or
1726             a hash. Hash is processed with L.
1727              
1728             Arguments are not quoted or escaped in any way. It's caller's job.
1729              
1730             =cut
1731              
1732             sub DateTimeIntervalFunction {
1733 2     2 1 3 my $self = shift;
1734 2         8 my %args = ( From => undef, To => undef, @_ );
1735              
1736             $_ = DBIx::SearchBuilder->CombineFunctionWithField(%$_)
1737 2         15 for grep ref, @args{'From', 'To'};
1738              
1739 2         8 return $self->_DateTimeIntervalFunction( %args );
1740             }
1741              
1742 0     0   0 sub _DateTimeIntervalFunction { return 'NULL' }
1743              
1744             =head2 NullsOrder
1745              
1746             Sets order of NULLs when sorting columns when called with mode,
1747             but only if DB supports it. Modes:
1748              
1749             =over 4
1750              
1751             =item * small
1752              
1753             NULLs are smaller then anything else, so come first when order
1754             is ASC and last otherwise.
1755              
1756             =item * large
1757              
1758             NULLs are larger then anything else.
1759              
1760             =item * first
1761              
1762             NULLs are always first.
1763              
1764             =item * last
1765              
1766             NULLs are always last.
1767              
1768             =item * default
1769              
1770             Return back to DB's default behaviour.
1771              
1772             =back
1773              
1774             When called without argument returns metadata required to generate
1775             SQL.
1776              
1777             =cut
1778              
1779             sub NullsOrder {
1780 327     327 1 507 my $self = shift;
1781              
1782 327 50       866 unless ($self->HasSupportForNullsOrder) {
1783 327 50       779 warn "No support for changing NULLs order" if @_;
1784 327         869 return undef;
1785             }
1786              
1787 0 0       0 if ( @_ ) {
1788 0   0     0 my $mode = shift || 'default';
1789 0 0       0 if ( $mode eq 'default' ) {
    0          
    0          
    0          
    0          
1790 0         0 delete $self->{'nulls_order'};
1791             }
1792             elsif ( $mode eq 'small' ) {
1793 0         0 $self->{'nulls_order'} = { ASC => 'NULLS FIRST', DESC => 'NULLS LAST' };
1794             }
1795             elsif ( $mode eq 'large' ) {
1796 0         0 $self->{'nulls_order'} = { ASC => 'NULLS LAST', DESC => 'NULLS FIRST' };
1797             }
1798             elsif ( $mode eq 'first' ) {
1799 0         0 $self->{'nulls_order'} = { ASC => 'NULLS FIRST', DESC => 'NULLS FIRST' };
1800             }
1801             elsif ( $mode eq 'last' ) {
1802 0         0 $self->{'nulls_order'} = { ASC => 'NULLS LAST', DESC => 'NULLS LAST' };
1803             }
1804             else {
1805 0         0 warn "'$mode' is not supported NULLs ordering mode";
1806 0         0 delete $self->{'nulls_order'};
1807             }
1808             }
1809              
1810 0 0       0 return undef unless $self->{'nulls_order'};
1811 0         0 return $self->{'nulls_order'};
1812             }
1813              
1814             =head2 HasSupportForNullsOrder
1815              
1816             Returns true value if DB supports adjusting NULLs order while sorting
1817             a column, for example C.
1818              
1819             =cut
1820              
1821             sub HasSupportForNullsOrder {
1822 328     328 1 758 return 0;
1823             }
1824              
1825             =head2 HasSupportForCombineSearchAndCount
1826              
1827             Returns true value if DB supports to combine search and count in single
1828             query.
1829              
1830             =cut
1831              
1832             sub HasSupportForCombineSearchAndCount {
1833 84     84 1 251 return 1;
1834             }
1835              
1836             =head2 HasSupportForEmptyString
1837              
1838             Returns true value if DB supports empty string.
1839              
1840             =cut
1841              
1842             sub HasSupportForEmptyString {
1843 0     0 1 0 return 1;
1844             }
1845              
1846             =head2 QuoteName
1847              
1848             Quote table or column name to avoid reserved word errors.
1849              
1850             Returns same value passed unless over-ridden in database-specific subclass.
1851              
1852             =cut
1853              
1854             # over-ride in subclass
1855             sub QuoteName {
1856 0     0 1 0 my ($self, $name) = @_;
1857             # use dbi built in quoting if we have a connection,
1858 0 0       0 if ($self->dbh) {
1859 0         0 return $self->dbh->quote_identifier($name);
1860             }
1861 0         0 warn "QuoteName called without a db handle";
1862 0         0 return $name;
1863             }
1864              
1865             =head2 DequoteName
1866              
1867             Undo the effects of QuoteName by removing quoting.
1868              
1869             =cut
1870              
1871             sub DequoteName {
1872 0     0 1 0 my ($self, $name) = @_;
1873 0 0       0 if ($self->dbh) {
1874             # 29 = SQL_IDENTIFIER_QUOTE_CHAR; see "perldoc DBI"
1875 0         0 my $quote_char = $self->dbh->get_info( 29 );
1876              
1877 0 0       0 if ($quote_char) {
1878 0 0       0 if ($name =~ /^$quote_char(.*)$quote_char$/) {
1879 0         0 return $1;
1880             }
1881             }
1882 0         0 return $name;
1883             }
1884 0         0 warn "DequoteName called without a db handle";
1885 0         0 return $name;
1886             }
1887              
1888             sub _ExtractBindValues {
1889 32     32   45 my $self = shift;
1890 32         70 my $string = shift;
1891 32   50     119 my $default_escape_char = shift || q{'};
1892 32 50       68 return $string unless defined $string;
1893              
1894 32         58 my $placeholder = '';
1895              
1896 32         510 my @chars = split //, $string;
1897 32         59 my $value = '';
1898 32         51 my $escape_char = $default_escape_char;
1899              
1900 32         49 my @values;
1901 32         60 my $in = 0; # keep state in the loop: is it in a quote?
1902 32         92 while ( defined( my $c = shift @chars ) ) {
1903 2542         3593 my $escaped;
1904 2542 100 100     5735 if ( $c eq $escape_char && $in ) {
1905 68 50       142 if ( $escape_char eq q{'} ) {
1906 68 100 50     243 if ( ( $chars[0] || '' ) eq q{'} ) {
1907 18         36 $c = shift @chars;
1908 18         32 $escaped = 1;
1909             }
1910             }
1911             else {
1912 0         0 $c = shift @chars;
1913 0         0 $escaped = 1;
1914             }
1915             }
1916              
1917 2542 100       4401 if ($in) {
1918 660 100       1271 if ( $c eq q{'} ) {
1919 68 100       150 if ( !$escaped ) {
1920 50         113 push @values, $value;
1921 50         85 $in = 0;
1922 50         87 $value = '';
1923 50         82 $escape_char = $default_escape_char;
1924 50         82 $placeholder .= '?';
1925 50         160 next;
1926             }
1927             }
1928 610         1499 $value .= $c;
1929             }
1930             else {
1931 1882 100 50     7571 if ( $c eq q{'} ) {
    50 66        
    50 66        
1932 50         131 $in = 1;
1933             }
1934              
1935             # Handle quoted string like e'foo\\bar'
1936             elsif ( lc $c eq 'e' && ( $chars[0] // '' ) eq q{'} ) {
1937 0         0 $escape_char = '\\';
1938             }
1939              
1940             # Handle numbers
1941             elsif ( $c =~ /[\d.]/ && $placeholder !~ /\w$/ ) { # Do not catch Groups_1.Name
1942 0         0 $value .= $c;
1943 0   0     0 while ( ( $chars[0] // '' ) =~ /[\d.]/ ) {
1944 0         0 $value .= shift @chars;
1945             }
1946              
1947 0         0 push @values, $value;
1948 0         0 $placeholder .= '?';
1949 0         0 $value = '';
1950             }
1951             else {
1952 1832         4701 $placeholder .= $c;
1953             }
1954             }
1955             }
1956 32         205 return ( $placeholder, @values );
1957             }
1958              
1959 21     21   75 sub _RequireQuotedTables { return 0 };
1960              
1961             =head2 DESTROY
1962              
1963             When we get rid of the Searchbuilder::Handle, we need to disconnect from the database
1964              
1965             =cut
1966              
1967             sub DESTROY {
1968 22     22   12422 my $self = shift;
1969 22 50       166 $self->Disconnect if $self->{'DisconnectHandleOnDestroy'};
1970 22         418 delete $DBIHandle{$self};
1971             }
1972              
1973             =head2 CastAsDecimal FIELD
1974              
1975             Cast the given field as decimal.
1976              
1977             E.g. on Pg, it's C.
1978              
1979             =cut
1980              
1981             sub CastAsDecimal {
1982 1     1 1 12 my $self = shift;
1983 1 50       6 my $field = shift or return;
1984 1         11 return "CAST($field AS DECIMAL)";
1985             }
1986              
1987             1;