Bugfix in SL/Menu und weitere stricts.
[kivitendo-erp.git] / SL / DBUtils.pm
1 package SL::DBUtils;
2
3 require Exporter;
4 our @ISA = qw(Exporter);
5
6 our @EXPORT = qw(conv_i conv_date conv_dateq do_query selectrow_query do_statement
7              dump_query quote_db_date
8              selectfirst_hashref_query selectfirst_array_query
9              selectall_hashref_query selectall_array_query
10              selectall_as_map
11              prepare_execute_query prepare_query
12              create_sort_spec does_table_exist
13              add_token);
14
15 sub conv_i {
16   my ($value, $default) = @_;
17   return (defined($value) && "$value" ne "") ? $value * 1 : $default;
18 }
19
20 sub conv_date {
21   my ($value) = @_;
22   return (defined($value) && "$value" ne "") ? $value : undef;
23 }
24
25 sub conv_dateq {
26   my ($value) = @_;
27   if (defined($value) && "$value" ne "") {
28     $value =~ s/\'/\'\'/g;
29     return "'$value'";
30   }
31   return "NULL";
32 }
33
34 sub do_query {
35   $main::lxdebug->enter_sub(2);
36
37   my ($form, $dbh, $query) = splice(@_, 0, 3);
38
39   dump_query(LXDebug->QUERY(), '', $query, @_);
40
41   my $result;
42   if (0 == scalar(@_)) {
43     $result = $dbh->do($query)            || $form->dberror($query);
44   } else {
45     $result = $dbh->do($query, undef, @_) || $form->dberror($query . " (" . join(", ", @_) . ")");
46   }
47
48   $main::lxdebug->leave_sub(2);
49
50   return $result;
51 }
52
53 sub selectrow_query { &selectfirst_array_query }
54
55 sub do_statement {
56   $main::lxdebug->enter_sub(2);
57
58   my ($form, $sth, $query) = splice(@_, 0, 3);
59
60   dump_query(LXDebug->QUERY(), '', $query, @_);
61
62   my $result;
63   if (0 == scalar(@_)) {
64     $result = $sth->execute()   || $form->dberror($query);
65   } else {
66     $result = $sth->execute(@_) || $form->dberror($query . " (" . join(", ", @_) . ")");
67   }
68
69   $main::lxdebug->leave_sub(2);
70
71   return $result;
72 }
73
74 sub dump_query {
75   my ($level, $msg, $query) = splice(@_, 0, 3);
76
77   my $self_filename = 'SL/DBUtils.pm';
78   my $filename      = $self_filename;
79   my ($caller_level, $line, $subroutine);
80   while ($filename eq $self_filename) {
81     (undef, $filename, $line, $subroutine) = caller $caller_level++;
82   }
83
84   while ($query =~ /\?/) {
85     my $value = shift(@_);
86     $value =~ s/\'/\\\'/g;
87     $value = "'${value}'";
88     $query =~ s/\?/$value/;
89   }
90
91   $query =~ s/[\n\s]+/ /g;
92
93   $msg .= " " if ($msg);
94
95   my $info = "$subroutine called from $filename:$line\n";
96
97   $main::lxdebug->message($level, $info . $msg . $query);
98 }
99
100 sub quote_db_date {
101   my ($str) = @_;
102
103   return "NULL" unless defined $str;
104   return "current_date" if $str =~ /current_date/;
105
106   $str =~ s/\'/\'\'/g;
107   return "'$str'";
108 }
109
110 sub prepare_query {
111   $main::lxdebug->enter_sub(2);
112
113   my ($form, $dbh, $query) = splice(@_, 0, 3);
114
115   dump_query(LXDebug->QUERY(), '', $query, @_);
116
117   my $sth = $dbh->prepare($query) || $form->dberror($query);
118
119   $main::lxdebug->leave_sub(2);
120
121   return $sth;
122 }
123
124 sub prepare_execute_query {
125   $main::lxdebug->enter_sub(2);
126
127   my ($form, $dbh, $query) = splice(@_, 0, 3);
128
129   dump_query(LXDebug->QUERY(), '', $query, @_);
130
131   my $sth = $dbh->prepare($query) || $form->dberror($query);
132   if (scalar(@_) != 0) {
133     $sth->execute(@_) || $form->dberror($query . " (" . join(", ", @_) . ")");
134   } else {
135     $sth->execute() || $form->dberror($query);
136   }
137
138   $main::lxdebug->leave_sub(2);
139
140   return $sth;
141 }
142
143 sub selectall_hashref_query {
144   $main::lxdebug->enter_sub(2);
145
146   my ($form, $dbh, $query) = splice(@_, 0, 3);
147
148   my $sth = prepare_execute_query($form, $dbh, $query, @_);
149   my $result = [];
150   while (my $ref = $sth->fetchrow_hashref()) {
151     push(@{ $result }, $ref);
152   }
153   $sth->finish();
154
155   $main::lxdebug->leave_sub(2);
156
157   return wantarray ? @{ $result } : $result;
158 }
159
160 sub selectall_array_query {
161   $main::lxdebug->enter_sub(2);
162
163   my ($form, $dbh, $query) = splice(@_, 0, 3);
164
165   my $sth = prepare_execute_query($form, $dbh, $query, @_);
166   my @result;
167   while (my ($value) = $sth->fetchrow_array()) {
168     push(@result, $value);
169   }
170   $sth->finish();
171
172   $main::lxdebug->leave_sub(2);
173
174   return @result;
175 }
176
177 sub selectfirst_hashref_query {
178   $main::lxdebug->enter_sub(2);
179
180   my ($form, $dbh, $query) = splice(@_, 0, 3);
181
182   my $sth = prepare_execute_query($form, $dbh, $query, @_);
183   my $ref = $sth->fetchrow_hashref();
184   $sth->finish();
185
186   $main::lxdebug->leave_sub(2);
187
188   return $ref;
189 }
190
191 sub selectfirst_array_query {
192   $main::lxdebug->enter_sub(2);
193
194   my ($form, $dbh, $query) = splice(@_, 0, 3);
195
196   my $sth = prepare_execute_query($form, $dbh, $query, @_);
197   my @ret = $sth->fetchrow_array();
198   $sth->finish();
199
200   $main::lxdebug->leave_sub(2);
201
202   return @ret;
203 }
204
205 sub selectall_as_map {
206   $main::lxdebug->enter_sub(2);
207
208   my ($form, $dbh, $query, $key_col, $value_col) = splice(@_, 0, 5);
209
210   my $sth = prepare_execute_query($form, $dbh, $query, @_);
211
212   my %hash;
213   if ('' eq ref $value_col) {
214     while (my $ref = $sth->fetchrow_hashref()) {
215       $hash{$ref->{$key_col}} = $ref->{$value_col};
216     }
217   } else {
218     while (my $ref = $sth->fetchrow_hashref()) {
219       $hash{$ref->{$key_col}} = { map { $_ => $ref->{$_} } @{ $value_col } };
220     }
221   }
222
223   $sth->finish();
224
225   $main::lxdebug->leave_sub(2);
226
227   return %hash;
228 }
229
230 sub create_sort_spec {
231   $main::lxdebug->enter_sub(2);
232
233   my %params = @_;
234
235   # Safety check:
236   $params{defs}    || die;
237   $params{default} || die;
238
239   # The definition of valid columns to sort by.
240   my $defs        = $params{defs};
241
242   # The column name to sort by. Use the default column name if none was given.
243   my %result      = ( 'column' => $params{column} || $params{default} );
244
245   # Overwrite the column name with the default column name if the other one is not valid.
246   $result{column} = $params{default} unless ($defs->{ $result{column} });
247
248   # The sort direction. true means 'sort ascending', false means 'sort descending'.
249   $result{dir}    = defined $params{dir}         ? $params{dir}
250                   : defined $params{default_dir} ? $params{default_dir}
251                   :                                1;
252   $result{dir}    = $result{dir} ?     1 :      0;
253   my $asc_desc    = $result{dir} ? 'ASC' : 'DESC';
254
255   # Create the SQL code.
256   my $cols        = $defs->{ $result{column} };
257   $result{sql}    = join ', ', map { "${_} ${asc_desc}" } @{ ref $cols eq 'ARRAY' ? $cols : [ $cols ] };
258
259   $main::lxdebug->leave_sub(2);
260
261   return %result;
262 }
263
264 sub does_table_exist {
265   $main::lxdebug->enter_sub(2);
266
267   my $dbh    = shift;
268   my $table  = shift;
269
270   my $result = 0;
271
272   if ($dbh) {
273     my $sth = $dbh->table_info('', '', $table, 'TABLE');
274     if ($sth) {
275       $result = $sth->fetchrow_hashref();
276       $sth->finish();
277     }
278   }
279
280   $main::lxdebug->leave_sub(2);
281
282   return $result;
283 }
284
285 # add token to values.
286 # usage:
287 #  add_token(
288 #    \@where_tokens,
289 #    \@where_values,
290 #    col => 'id',
291 #    val => [ 23, 34, 17 ]
292 #    esc => \&conf_i
293 #  )
294 #  will append to the given arrays:
295 #   -> 'id IN (?, ?, ?)'
296 #   -> (conv_i(23), conv_i(34), conv_i(17))
297 #
298 #  features:
299 #   - don't care if one or multiple values are given. singlewill result in 'col = ?'
300 #   - pass escape routines
301 #   - expand for future method
302 #   - no need to type "push @where_tokens, 'id = ?'" over and over again
303 sub add_token {
304   my $tokens = shift() || [];
305   my $values = shift() || [];
306   my %params = @_;
307   my $col    = $params{col};
308   my $val    = $params{val};
309   my $method = $params{method} || '=';
310   my $escape = $params{esc} || sub { $_ };
311
312   $val = [ $val ] unless ref $val eq 'ARRAY';
313
314   my %escapes = (
315     id     => \&conv_i,
316     date   => \&conv_date,
317   );
318
319   my %methods = (
320     '=' => sub {
321       my $col = shift;
322       return scalar @_ >  1 ? sprintf '%s IN (%s)', $col, join ', ', ("?") x scalar @_
323            : scalar @_ == 1 ? sprintf '%s = ?',     $col
324            :                  undef;
325     },
326   );
327
328   $method = $methods{$method} || $method;
329   $escape = $escapes{$escape} || $escape;
330
331   my $token = $method->($col, @{ $val });
332   my @vals  = map { $escape->($_) } @{ $val };
333
334   return unless $token;
335
336   push @{ $tokens }, $token;
337   push @{ $values }, @vals;
338
339   return ($token, @vals);
340 }
341
342 1;
343
344
345 __END__
346
347 =head1 NAME
348
349 SL::DBUTils.pm: All about Databaseconections in Lx
350
351 =head1 SYNOPSIS
352
353   use DBUtils;
354
355   conv_i($str, $default)
356   conv_date($str)
357   conv_dateq($str)
358   quote_db_date($date)
359
360   do_query($form, $dbh, $query)
361   do_statement($form, $sth, $query)
362
363   dump_query($level, $msg, $query)
364   prepare_execute_query($form, $dbh, $query)
365
366   my $all_results_ref       = selectall_hashref_query($form, $dbh, $query)
367   my $first_result_hash_ref = selectfirst_hashref_query($form, $dbh, $query);
368
369   my @first_result =  selectfirst_array_query($form, $dbh, $query);  # ==
370   my @first_result =  selectrow_query($form, $dbh, $query);
371
372   my %sort_spec = create_sort_spec(%params);
373
374 =head1 DESCRIPTION
375
376 DBUtils is the attempt to reduce the amount of overhead it takes to retrieve information from the database in Lx-Office. Previously it would take about 15 lines of code just to get one single integer out of the database, including failure procedures and importing the necessary packages. Debugging would take even more.
377
378 Using DBUtils most database procedures can be reduced to defining the query, executing it, and retrieving the result. Let DBUtils handle the rest. Whenever there is a database operation not covered in DBUtils, add it here, rather than working around it in the backend code.
379
380 DBUtils relies heavily on two parameters which have to be passed to almost every function: $form and $dbh.
381   - $form is used for error handling only. It can be omitted in theory, but should not.
382   - $dbh is a handle to the databe, as returned by the DBI::connect routine. If you don't have an active connectiong, you can query $form->get_standard_dbh() to get a generic no_auto connection. Don't forget to commit in this case!
383
384
385 Every function here should accomplish the follwing things:
386   - Easy debugging. Every handled query gets dumped via LXDebug, if specified there.
387   - Safe value binding. Although DBI is far from perfect in terms of binding, the rest of the bindings should happen here.
388   - Error handling. Should a query fail, an error message will be generated here instead of in the backend code invoking DBUtils.
389
390 Note that binding is not perfect here either...
391
392 =head2 QUOTING FUNCTIONS
393
394 =over 4
395
396 =item conv_i STR
397
398 =item conv_i STR,DEFAULT
399
400 Converts STR to an integer. If STR is empty, returns DEFAULT. If no DEFAULT is given, returns undef.
401
402 =item conv_date STR
403
404 Converts STR to a date string. If STR is emptry, returns undef.
405
406 =item conv_dateq STR
407
408 Database version of conv_date. Quotes STR before returning. Returns 'NULL' if STR is empty.
409
410 =item quote_db_date STR
411
412 Treats STR as a database date, quoting it. If STR equals current_date returns an escaped version which is treated as the current date by Postgres.
413 Returns 'NULL' if STR is empty.
414
415 =back
416
417 =head2 QUERY FUNCTIONS
418
419 =over 4
420
421 =item do_query FORM,DBH,QUERY,ARRAY
422
423 Uses DBI::do to execute QUERY on DBH using ARRAY for binding values. FORM is only needed for error handling, but should always be passed nevertheless. Use this for insertions or updates that don't need to be prepared.
424
425 Returns the result of DBI::do which is -1 in case of an error and the number of affected rows otherwise.
426
427 =item do_statement FORM,STH,QUERY,ARRAY
428
429 Uses DBI::execute to execute QUERY on DBH using ARRAY for binding values. As with do_query, FORM is only used for error handling. If you are unsure what to use, refer to the documentation of DBI::do and DBI::execute.
430
431 Returns the result of DBI::execute which is -1 in case of an error and the number of affected rows otherwise.
432
433 =item prepare_execute_query FORM,DBH,QUERY,ARRAY
434
435 Prepares and executes QUERY on DBH using DBI::prepare and DBI::execute. ARRAY is passed as binding values to execute.
436
437 =back
438
439 =head2 RETRIEVAL FUNCTIONS
440
441 =over 4
442
443 =item selectfirst_array_query FORM,DBH,QUERY,ARRAY
444
445 =item selectrow_query FORM,DBH,QUERY,ARRAY
446
447 Prepares and executes a query using DBUtils functions, retireves the first row from the database, and returns it as an arrayref of the first row.
448
449 =item selectfirst_hashref_query FORM,DBH,QUERY,ARRAY
450
451 Prepares and executes a query using DBUtils functions, retireves the first row from the database, and returns it as a hashref of the first row.
452
453 =item selectall_hashref_query FORM,DBH,QUERY,ARRAY
454
455 Prepares and executes a query using DBUtils functions, retireves all data from the database, and returns it in hashref mode. This is slightly confusing, as the data structure will actually be a reference to an array, containing hashrefs for each row.
456
457 =item selectall_as_map FORM,DBH,QUERY,KEY_COL,VALUE_COL,ARRAY
458
459 Prepares and executes a query using DBUtils functions, retireves all data from the database, and creates a hash from the results using KEY_COL as the column for the hash keys and VALUE_COL for its values.
460
461 =back
462
463 =head2 UTILITY FUNCTIONS
464
465 =over 4
466
467 =item create_sort_spec
468
469   params:
470     defs        => { },         # mandatory
471     default     => 'name',      # mandatory
472     column      => 'name',
473     default_dir => 0|1,
474     dir         => 0|1,
475
476   returns hash:
477     column      => 'name',
478     dir         => 0|1,
479     sql         => 'SQL code',
480
481 This function simplifies the creation of SQL code for sorting
482 columns. It uses a hashref of valid column names, the column name and
483 direction requested by the user, the application defaults for the
484 column name and the direction and returns the actual column name,
485 direction and SQL code that can be used directly in a query.
486
487 The parameter 'defs' is a hash reference. The keys are the column
488 names as they may come from the application. The values are either
489 scalars with SQL code or array references of SQL code. Example:
490
491 'defs' => { 'customername' => 'lower(customer.name)',
492             'address'      => [ 'lower(customer.city)', 'lower(customer.street)' ], }
493
494 'default' is the default column name to sort by. It must be a key of
495 'defs' and should not be come from user input.
496
497 The 'column' parameter is the column name as requested by the
498 application (e.g. if the user clicked on a column header in a
499 report). If it is invalid then the 'default' parameter will be used
500 instead.
501
502 'default_dir' is the default sort direction. A true value means 'sort
503 ascending', a false one 'sort descending'. 'default_dir' defaults to
504 '1' if undefined.
505
506 The 'dir' parameter is the sort direction as requested by the
507 application (e.g. if the user clicked on a column header in a
508 report). If it is undefined then the 'default_dir' parameter will be
509 used instead.
510
511 =back
512
513 =head2 DEBUG FUNCTIONS
514
515 =over 4
516
517 =item dump_query LEVEL,MSG,QUERY,ARRAY
518
519 Dumps a query using LXDebug->message, using LEVEL for the debug-level of LXDebug. If MSG is given, it preceeds the QUERY dump in the logfiles. ARRAY is used to interpolate the '?' placeholders in QUERY, the resulting QUERY can be copy-pasted into a database frontend for debugging. Note that this method is also automatically called by each of the other QUERY FUNCTIONS, so there is in general little need to invoke it manually.
520
521 =back
522
523 =head1 EXAMPLES
524
525 =over 4
526
527 =item Retrieving a whole table:
528
529   $query = qq|SELECT id, pricegroup FROM pricegroup|;
530   $form->{PRICEGROUPS} = selectall_hashref_query($form, $dbh, $query);
531
532 =item Retrieving a single value:
533
534   $query = qq|SELECT nextval('glid')|;
535   ($new_id) = selectrow_query($form, $dbh, $query);
536
537 =item Using binding values:
538
539   $query = qq|UPDATE ar SET paid = amount + paid, storno = 't' WHERE id = ?|;
540   do_query($form, $dbh, $query, $id);
541
542 =item A more complicated example, using dynamic binding values:
543
544   my @values;
545
546   if ($form->{language_values} ne "") {
547     $query = qq|SELECT l.id, l.description, tr.translation, tr.longdescription
548                   FROM language l
549                   LEFT OUTER JOIN translation tr ON (tr.language_id = l.id) AND (tr.parts_id = ?)|;
550     @values = (conv_i($form->{id}));
551   } else {
552     $query = qq|SELECT id, description FROM language|;
553   }
554
555   my $languages = selectall_hashref_query($form, $dbh, $query, @values);
556
557 =back
558
559 =head1 SEE ALSO
560
561 =head1 MODULE AUTHORS
562
563 Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>
564 Sven Schoeling E<lt>s.schoeling@linet-services.deE<gt>
565
566 =head1 DOCUMENTATION AUTHORS
567
568 Udo Spallek E<lt>udono@gmx.netE<gt>
569 Sven Schoeling E<lt>s.schoeling@linet-services.deE<gt>
570
571 =head1 COPYRIGHT AND LICENSE
572
573 Copyright 2007 by Lx-Office Community
574
575 This program is free software; you can redistribute it and/or modify
576 it under the terms of the GNU General Public License as published by
577 the Free Software Foundation; either version 2 of the License, or
578 (at your option) any later version.
579
580 This program is distributed in the hope that it will be useful,
581 but WITHOUT ANY WARRANTY; without even the implied warranty of
582 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
583 GNU General Public License for more details.
584 You should have received a copy of the GNU General Public License
585 along with this program; if not, write to the Free Software
586 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
587 =cut