Bug 6106: add item not respecting framework in acq The PrepareItemrecordDisplay sub...
[koha.git] / C4 / SQLHelper.pm
blob060b78f46e1e6973a7101708e45f03515ce46eb0
1 package C4::SQLHelper;
3 # Copyright 2009 Biblibre SARL
5 # This file is part of Koha.
7 # Koha is free software; you can redistribute it and/or modify it under the
8 # terms of the GNU General Public License as published by the Free Software
9 # Foundation; either version 2 of the License, or (at your option) any later
10 # version.
12 # Koha is distributed in the hope that it will be useful, but WITHOUT ANY
13 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
14 # A PARTICULAR PURPOSE. See the GNU General Public License for more details.
16 # You should have received a copy of the GNU General Public License along
17 # with Koha; if not, write to the Free Software Foundation, Inc.,
18 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21 use strict;
22 use warnings;
23 use List::MoreUtils qw(first_value any);
24 use C4::Context;
25 use C4::Dates qw(format_date_in_iso);
26 use C4::Debug;
27 require Exporter;
28 use vars qw($VERSION @ISA @EXPORT_OK %EXPORT_TAGS);
30 BEGIN {
31 # set the version for version checking
32 $VERSION = 0.5;
33 require Exporter;
34 @ISA = qw(Exporter);
35 @EXPORT_OK=qw(
36 InsertInTable
37 DeleteInTable
38 SearchInTable
39 UpdateInTable
40 GetPrimaryKeys
42 %EXPORT_TAGS = ( all =>[qw( InsertInTable DeleteInTable SearchInTable UpdateInTable GetPrimaryKeys)]
46 my $tablename;
47 my $hash;
49 =head1 NAME
51 C4::SQLHelper - Perl Module containing convenience functions for SQL Handling
53 =head1 SYNOPSIS
55 use C4::SQLHelper;
57 =head1 DESCRIPTION
59 This module contains routines for adding, modifying and Searching Data in MysqlDB
61 =head1 FUNCTIONS
63 =head2 SearchInTable
65 $hashref = &SearchInTable($tablename,$data, $orderby, $limit,
66 $columns_out, $filtercolumns, $searchtype);
69 $tablename Name of the table (string)
71 $data may contain
72 - string
74 - data_hashref : will be considered as an AND of all the data searched
76 - data_array_ref on hashrefs : Will be considered as an OR of Datahasref elements
78 $orderby is an arrayref of hashref with fieldnames as key and 0 or 1 as values (ASCENDING or DESCENDING order)
80 $limit is an array ref on 2 values in order to limit results to MIN..MAX
82 $columns_out is an array ref on field names is used to limit results on those fields (* by default)
84 $filtercolums is an array ref on field names : is used to limit expansion of research for strings
86 $searchtype is string Can be "start_with" or "exact"
88 =cut
90 sub SearchInTable{
91 my ($tablename,$filters,$orderby, $limit, $columns_out, $filter_columns,$searchtype) = @_;
92 $searchtype||="exact";
93 my $dbh = C4::Context->dbh;
94 $columns_out||=["*"];
95 my $sql = do { local $"=', ';
96 qq{ SELECT @$columns_out from $tablename}
98 my $row;
99 my $sth;
100 my ($keys,$values)=_filter_fields($tablename,$filters,$searchtype,$filter_columns);
101 if ($keys){
102 my @criteria=grep{defined($_) && $_ !~/^\W$/ }@$keys;
103 if (@criteria) {
104 $sql.= do { local $"=') AND (';
105 qq{ WHERE (@criteria) }
109 if ($orderby){
110 #Order by desc by default
111 my @orders;
112 foreach my $order (@$orderby){
113 push @orders,map{ "$_".($order->{$_}? " DESC " : "") } keys %$order;
115 $sql.= do { local $"=', ';
116 qq{ ORDER BY @orders}
119 if ($limit){
120 $sql.=qq{ LIMIT }.join(",",@$limit);
123 $debug && $values && warn $sql," ",join(",",@$values);
124 $sth = $dbh->prepare_cached($sql);
125 eval{$sth->execute(@$values)};
126 warn $@ if ($@ && $debug);
127 my $results = $sth->fetchall_arrayref( {} );
128 return $results;
131 =head2 InsertInTable
133 $data_id_in_table = &InsertInTable($tablename,$data_hashref,$withprimarykeys);
135 Insert Data in table and returns the id of the row inserted
137 =cut
139 sub InsertInTable{
140 my ($tablename,$data,$withprimarykeys) = @_;
141 my $dbh = C4::Context->dbh;
142 my ($keys,$values)=_filter_hash($tablename,$data,($withprimarykeys?"exact":0));
143 my $query = qq{ INSERT INTO $tablename SET }.join(", ",@$keys);
145 $debug && warn $query, join(",",@$values);
146 my $sth = $dbh->prepare_cached($query);
147 eval{$sth->execute(@$values)};
148 warn $@ if ($@ && $debug);
150 return $dbh->last_insert_id(undef, undef, $tablename, undef);
153 =head2 UpdateInTable
155 $status = &UpdateInTable($tablename,$data_hashref);
157 Update Data in table and returns the status of the operation
159 =cut
161 sub UpdateInTable{
162 my ($tablename,$data) = @_;
163 my @field_ids=GetPrimaryKeys($tablename);
164 my @ids=@$data{@field_ids};
165 my $dbh = C4::Context->dbh;
166 my ($keys,$values)=_filter_hash($tablename,$data,0);
167 my $query =
168 qq{ UPDATE $tablename
169 SET }.join(",",@$keys).qq{
170 WHERE }.join (" AND ",map{ "$_=?" }@field_ids);
171 $debug && warn $query, join(",",@$values,@ids);
173 my $sth = $dbh->prepare_cached($query);
174 my $result;
175 eval{$result=$sth->execute(@$values,@ids)};
176 warn $@ if ($@ && $debug);
177 return $result;
180 =head2 DeleteInTable
182 $status = &DeleteInTable($tablename,$data_hashref);
184 Delete Data in table and returns the status of the operation
186 =cut
188 sub DeleteInTable{
189 my ($tablename,$data) = @_;
190 my $dbh = C4::Context->dbh;
191 my ($keys,$values)=_filter_fields($tablename,$data,1);
192 if ($keys){
193 my $query = do { local $"=') AND (';
194 qq{ DELETE FROM $tablename WHERE (@$keys)};
196 $debug && warn $query, join(",",@$values);
197 my $sth = $dbh->prepare_cached($query);
198 my $result;
199 eval{$result=$sth->execute(@$values)};
200 warn $@ if ($@ && $debug);
201 return $result;
205 =head2 GetPrimaryKeys
207 @primarykeys = &GetPrimaryKeys($tablename)
209 Get the Primary Key field names of the table
211 =cut
213 sub GetPrimaryKeys($) {
214 my $tablename=shift;
215 my $hash_columns=_get_columns($tablename);
216 return grep { $hash_columns->{$_}->{'Key'} =~/PRI/i} keys %$hash_columns;
219 =head2 _get_columns
221 _get_columns($tablename)
223 Given a tablename
224 Returns a hashref of all the fieldnames of the table
225 With
227 Type
228 Default
230 =cut
232 sub _get_columns($) {
233 my ($tablename)=@_;
234 my $dbh=C4::Context->dbh;
235 my $sth=$dbh->prepare_cached(qq{SHOW COLUMNS FROM $tablename });
236 $sth->execute;
237 my $columns= $sth->fetchall_hashref(qw(Field));
240 =head2 _filter_columns
242 _filter_columns($tablename,$research, $filtercolumns)
244 Given
245 - a tablename
246 - indicator on purpose whether all fields should be returned or only non Primary keys
247 - array_ref to columns to limit to
249 Returns an array of all the fieldnames of the table
250 If it is not for research purpose, filter primary keys
252 =cut
254 sub _filter_columns ($$;$) {
255 my ($tablename,$research, $filtercolumns)=@_;
256 if ($filtercolumns){
257 return (@$filtercolumns);
259 else {
260 my $columns=_get_columns($tablename);
261 if ($research){
262 return keys %$columns;
264 else {
265 return grep {my $column=$_; any {$_ ne $column }GetPrimaryKeys($tablename) } keys %$columns;
269 =head2 _filter_fields
271 _filter_fields
273 Given
274 - a tablename
275 - a string or a hashref (containing, fieldnames and datatofilter) or an arrayref to one of those elements
276 - an indicator of operation whether it is a wide research or a narrow one
277 - an array ref to columns to restrict string filter to.
279 Returns a ref of key array to use in SQL functions
280 and a ref to value array
282 =cut
284 sub _filter_fields{
285 my ($tablename,$filter_input,$searchtype,$filtercolumns)=@_;
286 my @keys;
287 my @values;
288 if (ref($filter_input) eq "HASH"){
289 my ($keys, $values) = _filter_hash($tablename,$filter_input, $searchtype);
290 if ($keys){
291 my $stringkey="(".join (") AND (",@$keys).")";
292 return [$stringkey],$values;
294 else {
295 return ();
297 } elsif (ref($filter_input) eq "ARRAY"){
298 foreach my $element_data (@$filter_input){
299 my ($localkeys,$localvalues)=_filter_fields($tablename,$element_data,$searchtype,$filtercolumns);
300 if ($localkeys){
301 @$localkeys=grep{defined($_) && $_ !~/^\W*$/}@$localkeys;
302 my $string=do{
303 local $"=") OR (";
304 qq{(@$localkeys)}
306 push @keys, $string;
307 push @values, @$localvalues;
311 else{
312 $debug && warn "filterstring : $filter_input";
313 my ($keys, $values) = _filter_string($tablename,$filter_input, $searchtype,$filtercolumns);
314 if ($keys){
315 my $stringkey="(".join (") AND (",@$keys).")";
316 return [$stringkey],$values;
318 else {
319 return ();
323 return (\@keys,\@values);
326 sub _filter_hash{
327 my ($tablename,$filter_input, $searchtype)=@_;
328 my (@values, @keys);
329 my $columns= _get_columns($tablename);
330 my @columns_filtered= _filter_columns($tablename,$searchtype);
332 #Filter Primary Keys of table
333 my $elements=join "|",@columns_filtered;
334 foreach my $field (grep {/\b($elements)\b/} keys %$filter_input){
335 ## supposed to be a hash of simple values, hashes of arrays could be implemented
336 $filter_input->{$field}=format_date_in_iso($filter_input->{$field}) if ($columns->{$field}{Type}=~/date/ && $filter_input->{$field} !~C4::Dates->regexp("iso"));
337 my ($tmpkeys, $localvalues)=_Process_Operands($filter_input->{$field},"$tablename.$field",$searchtype,$columns);
338 if (@$tmpkeys){
339 push @values, @$localvalues;
340 push @keys, @$tmpkeys;
343 if (@keys){
344 return (\@keys,\@values);
346 else {
347 return ();
351 sub _filter_string{
352 my ($tablename,$filter_input, $searchtype,$filtercolumns)=@_;
353 return () unless($filter_input);
354 my @operands=split / /,$filter_input;
355 my @columns_filtered= _filter_columns($tablename,$searchtype,$filtercolumns);
356 my $columns= _get_columns($tablename);
357 my (@values,@keys);
358 foreach my $operand (@operands){
359 my @localkeys;
360 foreach my $field (@columns_filtered){
361 my ($tmpkeys, $localvalues)=_Process_Operands($operand,"$tablename.$field",$searchtype,$columns);
362 if ($tmpkeys){
363 push @values,@$localvalues;
364 push @localkeys,@$tmpkeys;
367 my $sql= join (' OR ', @localkeys);
368 push @keys, $sql;
371 if (@keys){
372 return (\@keys,\@values);
374 else {
375 return ();
378 sub _Process_Operands{
379 my ($operand, $field, $searchtype,$columns)=@_;
380 my @values;
381 my @tmpkeys;
382 my @localkeys;
383 push @tmpkeys, " $field = ? ";
384 push @values, $operand;
385 #By default, exact search
386 if (!$searchtype ||$searchtype eq "exact"){
387 return \@tmpkeys,\@values;
389 my $col_field=(index($field,".")>0?substr($field, index($field,".")+1):$field);
390 if ($field=~/(?<!zip)code|(?<!card)number/ && $searchtype ne "exact"){
391 push @tmpkeys,(" $field= '' ","$field IS NULL");
393 if ($columns->{$col_field}->{Type}=~/varchar|text/i){
394 my @localvaluesextended;
395 if ($searchtype eq "contain"){
396 push @tmpkeys,(" $field LIKE ? ");
397 push @localvaluesextended,("\%$operand\%") ;
399 if ($searchtype eq "field_start_with"){
400 push @tmpkeys,("$field LIKE ?");
401 push @localvaluesextended, ("$operand\%") ;
403 if ($searchtype eq "start_with"){
404 push @tmpkeys,("$field LIKE ?","$field LIKE ?");
405 push @localvaluesextended, ("$operand\%", " $operand\%") ;
407 push @values,@localvaluesextended;
409 push @localkeys,qq{ (}.join(" OR ",@tmpkeys).qq{) };
410 return (\@localkeys,\@values);