package My::DB; use strict; use 5.004; use DBI; use vars qw(%c); use constant DEBUG => 0; %c = ( db => { DB_NAME => 'foo', SERVER => 'localhost', USER => 'put_username_here', USER_PASSWD => 'put_passwd_here', }, ); use Carp qw(croak verbose); #local $SIG{__WARN__} = \&Carp::cluck; # untaint the path by explicit setting local $ENV{PATH} = '/bin:/usr/bin'; ####### sub new { my $proto = shift; my $class = ref($proto) || $proto; my $self = {}; # connect to the DB, Apache::DBI takes care of caching the connections # save into a dbh - Database handle object $self->{dbh} = DBI->connect("DBI:mysql:$c{db}{DB_NAME}::$c{db}{SERVER}", $c{db}{USER}, $c{db}{USER_PASSWD}, { PrintError => 1, # warn() on errors RaiseError => 0, # don't die on error AutoCommit => 1, # commit executes immediately } ) or die "Cannot connect to database: $DBI::errstr"; # we want to die on errors if in debug mode $self->{dbh}->{RaiseError} = 1 if DEBUG; # init the sth - Statement handle object $self->{sth} = ''; bless ($self, $class); $self; } # end of sub new ###################################################################### ################################### ### ### ### SQL Functions ### ### ### ################################### ###################################################################### # print debug messages sub d{ # we want to print the trace in debug mode print "
".join("
", @_)."
\n" if DEBUG; } # end of sub d ###################################################################### # return a count of matched rows, by conditions # # $count = sql_count_matched($table_name,\@conditions,\@restrictions); # # conditions must be an array so we can pass more than one column with # the same name. # # @conditions = ( column => ['comp_sign','value'], # foo => ['>',15], # foo => ['<',30], # ); # # The sub knows automatically to detect and quote strings # # Restrictions are the list of restrictions like ('order by email') # ########################## sub sql_count_matched{ my $self = shift; my $table = shift || ''; my $r_conds = shift || []; my $r_restr = shift || []; # we want to print the trace in debug mode d( "[".(caller(2))[3]." - ".(caller(1))[3]." - ". (caller(0))[3]."]") if DEBUG; # build the query my $do_sql = "SELECT COUNT(*) FROM $table "; my @where = (); for(my $i=0;$i<@{$r_conds};$i=$i+2) { push @where, join " ", $$r_conds[$i], $$r_conds[$i+1][0], sql_quote(sql_escape($$r_conds[$i+1][1])); } # Add the where clause if we have one $do_sql .= "WHERE ". join " AND ", @where if @where; # restrictions (DONT put commas!) $do_sql .= " ". join " ", @{$r_restr} if @{$r_restr}; d("SQL: $do_sql") if DEBUG; # do query $self->{sth} = $self->{dbh}->prepare($do_sql); $self->{sth}->execute(); my ($count) = $self->{sth}->fetchrow_array; d("Result: $count") if DEBUG; $self->{sth}->finish; return $count; } # end of sub sql_count_matched ###################################################################### # return a count of matched distinct rows, by conditions # # $count = sql_count_matched_distinct($table_name,\@conditions,\@restrictions); # # conditions must be an array so we can path more than one column with # the same name. # # @conditions = ( column => ['comp_sign','value'], # foo => ['>',15], # foo => ['<',30], # ); # # The sub knows automatically to detect and quote strings # # Restrictions are the list of restrictions like ('order by email') # # This a slow implementation - because it cannot use select(*), but # brings all the records in first and then counts them. In the next # version of mysql there will be an operator 'select (distinct *)' # which will make things much faster, so we will just change the # internals of this sub, without changing the code itself. # ############################## sub sql_count_matched_distinct{ my $self = shift; my $table = shift || ''; my $r_conds = shift || []; my $r_restr = shift || []; # we want to print the trace in debug mode d( "[".(caller(2))[3]." - ".(caller(1))[3]." - ". (caller(0))[3]."]") if DEBUG; # build the query my $do_sql = "SELECT DISTINCT * FROM $table "; my @where = (); for(my $i=0;$i<@{$r_conds};$i=$i+2) { push @where, join " ", $$r_conds[$i], $$r_conds[$i+1][0], sql_quote(sql_escape($$r_conds[$i+1][1])); } # Add the where clause if we have one $do_sql .= "WHERE ". join " AND ", @where if @where; # restrictions (DONT put commas!) $do_sql .= " ". join " ", @{$r_restr} if @{$r_restr}; d("SQL: $do_sql") if DEBUG; # do query # $self->{sth} = $self->{dbh}->prepare($do_sql); # $self->{sth}->execute(); my $count = @{$self->{dbh}->selectall_arrayref($do_sql)}; # my ($count) = $self->{sth}->fetchrow_array; d("Result: $count") if DEBUG; # $self->{sth}->finish; return $count; } # end of sub sql_count_matched_distinct ###################################################################### # return a single (first) matched value or undef, by conditions and # restrictions # # sql_get_matched_value($table_name,$column,\@conditions,\@restrictions); # # column is a name of the column # # conditions must be an array so we can path more than one column with # the same name. # @conditions = ( column => ['comp_sign','value'], # foo => ['>',15], # foo => ['<',30], # ); # The sub knows automatically to detect and quote strings # # restrictions is a list of restrictions like ('order by email') # ########################## sub sql_get_matched_value{ my $self = shift; my $table = shift || ''; my $column = shift || ''; my $r_conds = shift || []; my $r_restr = shift || []; # we want to print in the trace debug mode d( "[".(caller(2))[3]." - ".(caller(1))[3]." - ". (caller(0))[3]."]") if DEBUG; # build the query my $do_sql = "SELECT $column FROM $table "; my @where = (); for(my $i=0;$i<@{$r_conds};$i=$i+2) { push @where, join " ", $$r_conds[$i], $$r_conds[$i+1][0], sql_quote(sql_escape($$r_conds[$i+1][1])); } # Add the where clause if we have one $do_sql .= " WHERE ". join " AND ", @where if @where; # restrictions (DONT put commas!) $do_sql .= " ". join " ", @{$r_restr} if @{$r_restr}; d("SQL: $do_sql") if DEBUG; # do query return $self->{dbh}->selectrow_array($do_sql); } # end of sub sql_get_matched_value ###################################################################### # return a single row of first matched rows, by conditions and # restrictions. The row is being inserted into @results_row array # (value1,value2,...) or empty () if none matched # # sql_get_matched_row(\@results_row,$table_name,\@columns,\@conditions,\@restrictions); # # columns is a list of columns to be returned (username, fname,...) # # conditions must be an array so we can path more than one column with # the same name. # @conditions = ( column => ['comp_sign','value'], # foo => ['>',15], # foo => ['<',30], # ); # The sub knows automatically to detect and quote strings # # restrictions is a list of restrictions like ('order by email') # ########################## sub sql_get_matched_row{ my $self = shift; my $r_row = shift || {}; my $table = shift || ''; my $r_cols = shift || []; my $r_conds = shift || []; my $r_restr = shift || []; # we want to print in the trace debug mode d( "[".(caller(2))[3]." - ".(caller(1))[3]." - ". (caller(0))[3]."]") if DEBUG; # build the query my $do_sql = "SELECT "; $do_sql .= join ",", @{$r_cols} if @{$r_cols}; $do_sql .= " FROM $table "; my @where = (); for(my $i=0;$i<@{$r_conds};$i=$i+2) { push @where, join " ", $$r_conds[$i], $$r_conds[$i+1][0], sql_quote(sql_escape($$r_conds[$i+1][1])); } # Add the where clause if we have one $do_sql .= " WHERE ". join " AND ", @where if @where; # restrictions (DONT put commas!) $do_sql .= " ". join " ", @{$r_restr} if @{$r_restr}; d("SQL: $do_sql") if DEBUG; # do query @{$r_row} = $self->{dbh}->selectrow_array($do_sql); } # end of sub sql_get_matched_row ###################################################################### # return a ref to hash of single matched row, by conditions # and restrictions. return undef if nothing matched. # (column1 => value1, column2 => value2) or empty () if non matched # # sql_get_hash_ref($table_name,\@columns,\@conditions,\@restrictions); # # columns is a list of columns to be returned (username, fname,...) # # conditions must be an array so we can path more than one column with # the same name. # @conditions = ( column => ['comp_sign','value'], # foo => ['>',15], # foo => ['<',30], # ); # The sub knows automatically to detect and quote strings # # restrictions is a list of restrictions like ('order by email') # ########################## sub sql_get_hash_ref{ my $self = shift; my $table = shift || ''; my $r_cols = shift || []; my $r_conds = shift || []; my $r_restr = shift || []; # we want to print in the trace debug mode d( "[".(caller(2))[3]." - ".(caller(1))[3]." - ". (caller(0))[3]."]") if DEBUG; # build the query my $do_sql = "SELECT "; $do_sql .= join ",", @{$r_cols} if @{$r_cols}; $do_sql .= " FROM $table "; my @where = (); for(my $i=0;$i<@{$r_conds};$i=$i+2) { push @where, join " ", $$r_conds[$i], $$r_conds[$i+1][0], sql_quote(sql_escape($$r_conds[$i+1][1])); } # Add the where clause if we have one $do_sql .= " WHERE ". join " AND ", @where if @where; # restrictions (DONT put commas!) $do_sql .= " ". join " ", @{$r_restr} if @{$r_restr}; d("SQL: $do_sql") if DEBUG; # do query $self->{sth} = $self->{dbh}->prepare($do_sql); $self->{sth}->execute(); return $self->{sth}->fetchrow_hashref; } # end of sub sql_get_hash_ref ###################################################################### # returns a reference to an array, matched by conditions and # restrictions, which contains one reference to array per row. If # there are no rows to return, returns a reference to an empty array: # [ # [array1], # ...... # [arrayN], # ]; # # $ref = sql_get_matched_rows_ary_ref($table_name,\@columns,\@conditions,\@restrictions); # # columns is a list of columns to be returned (username, fname,...) # # conditions must be an array so we can path more than one column with # the same name. @conditions are being cancatenated with AND # @conditions = ( column => ['comp_sign','value'], # foo => ['>',15], # foo => ['<',30], # ); # results in # WHERE foo > 15 AND foo < 30 # # to make an OR logic use (then ANDed ) # @conditions = ( column => ['comp_sign',['value1','value2']], # foo => ['=',[15,24] ], # bar => ['=',[16,21] ], # ); # results in # WHERE (foo = 15 OR foo = 24) AND (bar = 16 OR bar = 21) # # The sub knows automatically to detect and quote strings # # restrictions is a list of restrictions like ('order by email') # ########################## sub sql_get_matched_rows_ary_ref{ my $self = shift; my $table = shift || ''; my $r_cols = shift || []; my $r_conds = shift || []; my $r_restr = shift || []; # we want to print in the trace debug mode d( "[".(caller(2))[3]." - ".(caller(1))[3]." - ". (caller(0))[3]."]") if DEBUG; # build the query my $do_sql = "SELECT "; $do_sql .= join ",", @{$r_cols} if @{$r_cols}; $do_sql .= " FROM $table "; my @where = (); for(my $i=0;$i<@{$r_conds};$i=$i+2) { if (ref $$r_conds[$i+1][1] eq 'ARRAY') { # multi condition for the same field/comparator to be ORed push @where, map {"($_)"} join " OR ", map { join " ", $r_conds->[$i], $r_conds->[$i+1][0], sql_quote(sql_escape($_)); } @{$r_conds->[$i+1][1]}; } else { # single condition for the same field/comparator push @where, join " ", $r_conds->[$i], $r_conds->[$i+1][0], sql_quote(sql_escape($r_conds->[$i+1][1])); } } # end of for(my $i=0;$i<@{$r_conds};$i=$i+2 # Add the where clause if we have one $do_sql .= " WHERE ". join " AND ", @where if @where; # restrictions (DONT put commas!) $do_sql .= " ". join " ", @{$r_restr} if @{$r_restr}; d("SQL: $do_sql") if DEBUG; # do query return $self->{dbh}->selectall_arrayref($do_sql); } # end of sub sql_get_matched_rows_ary_ref ###################################################################### # insert a single row into a DB # # sql_insert_row($table_name,\%data,$delayed); # # data is hash of type (column1 => value1 ,column2 => value2 , ) # # $delayed: 1 => do delayed insert, 0 or none passed => immediate # # * The sub knows automatically to detect and quote strings # # * The insert id delayed, so the user will not wait untill the insert # will be completed, if many select queries are running # ########################## sub sql_insert_row{ my $self = shift; my $table = shift || ''; my $r_data = shift || {}; my $delayed = (shift) ? 'DELAYED' : ''; # we want to print in the trace debug mode d( "[".(caller(2))[3]." - ".(caller(1))[3]." - ". (caller(0))[3]."]") if DEBUG; # build the query my $do_sql = "INSERT $delayed INTO $table "; $do_sql .= "(".join(",",keys %{$r_data}).")"; $do_sql .= " VALUES ("; $do_sql .= join ",", sql_quote(sql_escape( values %{$r_data} ) ); $do_sql .= ")"; d("SQL: $do_sql") if DEBUG; # do query $self->{sth} = $self->{dbh}->prepare($do_sql); $self->{sth}->execute(); } # end of sub sql_insert_row ###################################################################### # update rows in a DB by condition # # sql_update_rows($table_name,\%data,\@conditions,$delayed); # # data is hash of type (column1 => value1 ,column2 => value2 , ) # # conditions must be an array so we can path more than one column with # the same name. # @conditions = ( column => ['comp_sign','value'], # foo => ['>',15], # foo => ['<',30], # ); # # $delayed: 1 => do delayed insert, 0 or none passed => immediate # # * The sub knows automatically to detect and quote strings # # ########################## sub sql_update_rows{ my $self = shift; my $table = shift || ''; my $r_data = shift || {}; my $r_conds = shift || []; my $delayed = (shift) ? 'LOW_PRIORITY' : ''; # we want to print in the trace debug mode d( "[".(caller(2))[3]." - ".(caller(1))[3]." - ". (caller(0))[3]."]") if DEBUG; # build the query my $do_sql = "UPDATE $delayed $table SET "; $do_sql .= join ",", map { "$_=".join "",sql_quote(sql_escape($$r_data{$_})) } keys %{$r_data}; my @where = (); for(my $i=0;$i<@{$r_conds};$i=$i+2) { push @where, join " ", $$r_conds[$i], $$r_conds[$i+1][0], sql_quote(sql_escape($$r_conds[$i+1][1])); } # Add the where clause if we have one $do_sql .= " WHERE ". join " AND ", @where if @where; d("SQL: $do_sql") if DEBUG; # do query $self->{sth} = $self->{dbh}->prepare($do_sql); $self->{sth}->execute(); # my ($count) = $self->{sth}->fetchrow_array; # # d("Result: $count") if DEBUG; } # end of sub sql_update_rows ###################################################################### # delete rows from DB by condition # # sql_delete_rows($table_name,\@conditions); # # conditions must be an array so we can path more than one column with # the same name. # @conditions = ( column => ['comp_sign','value'], # foo => ['>',15], # foo => ['<',30], # ); # # * The sub knows automatically to detect and quote strings # # ########################## sub sql_delete_rows{ my $self = shift; my $table = shift || ''; my $r_conds = shift || []; # we want to print in the trace debug mode d( "[".(caller(2))[3]." - ".(caller(1))[3]." - ". (caller(0))[3]."]") if DEBUG; # build the query my $do_sql = "DELETE FROM $table "; my @where = (); for(my $i=0;$i<@{$r_conds};$i=$i+2) { push @where, join " ", $$r_conds[$i], $$r_conds[$i+1][0], sql_quote(sql_escape($$r_conds[$i+1][1])); } # Must be very careful with deletes, imagine somehow @where is # not getting set, "DELETE FROM NAME" deletes the contents of the table warn("Attempt to delete a whole table $table from DB\n!!!"),return unless @where; # Add the where clause if we have one $do_sql .= " WHERE ". join " AND ", @where; d("SQL: $do_sql") if DEBUG; # do query $self->{sth} = $self->{dbh}->prepare($do_sql); $self->{sth}->execute(); } # end of sub sql_delete_rows ###################################################################### # executes the passed query and returns a reference to an array which # contains one reference per row. If there are no rows to return, # returns a reference to an empty array. # # $r_array = sql_execute_and_get_r_array($query); # # ########################## sub sql_execute_and_get_r_array{ my $self = shift; my $do_sql = shift || ''; # we want to print in the trace debug mode d( "[".(caller(2))[3]." - ".(caller(1))[3]." - ". (caller(0))[3]."]") if DEBUG; d("SQL: $do_sql") if DEBUG; $self->{dbh}->selectall_arrayref($do_sql); } # end of sub sql_execute_and_get_r_array ###################################################################### # lock the passed tables in the requested mode (READ|WRITE) and set # internal flag to handle possible user abortions, so the tables will # be unlocked thru the END{} block # # sql_lock_tables('table1','lockmode',..,'tableN','lockmode' # lockmode = (READ | WRITE) # # _side_effect_ $self->{lock} = 'On'; # ########################## sub sql_lock_tables{ my $self = shift; my %modes = @_; return unless %modes; my $do_sql = 'LOCK TABLES '; $do_sql .= join ",", map {"$_ $modes{$_}"} keys %modes; # we want to print the trace in debug mode d( "[".(caller(2))[3]." - ".(caller(1))[3]." - ". (caller(0))[3]."]") if DEBUG; d("SQL: $do_sql") if DEBUG; $self->{sth} = $self->{dbh}->prepare($do_sql); $self->{sth}->execute(); # Enough to set only one lock, unlock will remove them all $self->{lock} = 'On'; } # end of sub sql_lock_tables ###################################################################### # unlock all tables, unset internal flag to handle possible user # abortions, so the tables will be unlocked thru the END{} block # # sql_unlock_tables() # # _side_effect_: delete $self->{lock} # ########################## sub sql_unlock_tables{ my $self = shift; # we want to print the trace in debug mode d( "[".(caller(2))[3]." - ".(caller(1))[3]." - ". (caller(0))[3]."]") if DEBUG; $self->{dbh}->do("UNLOCK TABLES"); # Enough to set only one lock, unlock will remove them all delete $self->{lock}; } # end of sub sql_unlock_tables # # # return current date formatted for a DATE field type # YYYYMMDD # # Note: since this function actually doesn't need an object it's being # called without parameter as well as procedural call ############ sub sql_date{ my $self = shift; my ($mday,$mon,$year) = (localtime)[3..5]; return sprintf "%0.4d%0.2d%0.2d",1900+$year,++$mon,$mday; } # end of sub sql_date # # # return current date formatted for a DATE field type # YYYYMMDDHHMMSS # # Note: since this function actually doesn't need an object it's being # called without parameter as well as procedural call ############ sub sql_datetime{ my $self = shift; my ($sec,$min,$hour,$mday,$mon,$year) = localtime(); return sprintf "%0.4d%0.2d%0.2d%0.2d%0.2d%0.2d",1900+$year,++$mon,$mday,$hour,$min,$sec; } # end of sub sql_datetime # Quote the list of parameters. Parameters consisting entirely of # digits (i.e. integers) are unquoted. # print sql_quote("one",2,"three"); => 'one', 2, 'three' ############# sub sql_quote{ map{ /^(\d+|NULL)$/ ? $_ : "\'$_\'" } @_ } # Escape the list of parameters (all unsafe chars like ",' are escaped) # We make a copy of @_ since we might try to change the passed values, # producing an error when modification of a read-only value is attempted ############## sub sql_escape{ my @a = @_; map { s/([\'\\])/\\$1/g;$_} @a } # DESTROY makes all kinds of cleanups if the fuctions were interuppted # before their completion and haven't had a chance to make a clean up. ########### sub DESTROY{ my $self = shift; $self->sql_unlock_tables() if $self->{lock}; $self->{sth}->finish if $self->{sth}; $self->{dbh}->disconnect if $self->{dbh}; } # end of sub DESTROY # Don't remove 1;