The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
################################################
#
# Module   : CTK::DBI
# Style    : OOP
# DATE     : 03.03.2013
# Revision : $Revision: 180 $
# Id       : $Id: DBI.pm 180 2014-04-14 19:59:32Z minus $
#
# Äîñóòï ê áàçàì äàííûõ íà îñíîâå ìîäóëÿ DBI. ìîäóëü îáëåã÷àåò äîñòóï ê äàííûì è íåñêîëüêî ñõîæ
# ñ ìîäóëåì multistore â ïðîåêòå MPMinus
#
# Ìîäóëü îñíàùåí îáîëî÷êîé êîíòðîëÿ âðåìåíè âûïîëíåíèÿ çàïðîñîâ: Sys::SigAction
#
# Copyright (c) 1998-2012 D&D Corporation. All rights reserved
# Copyright (C) 1998-2012 Lepenkov Sergej (Serz Minus) <minus@mail333.com>
#
#
################################################
package CTK::DBI; # $Id: DBI.pm 180 2014-04-14 19:59:32Z minus $
use strict;

=head1 NAME

CTK::DBI - Database independent interface for CTKlib

=head1 VERSION

Version 2.25

=head1 REVISION

$Revision: 180 $

=head1 SYNOPSIS

    use CTK::DBI;

    # MySQL connect
    my $mso = new CTK::DBI(
            -dsn        => 'DBI:mysql:database=TEST;host=192.168.1.1',
            -user       => 'login',
            -pass       => 'password',
            -connect_to => 5,
            -request_to => 60
            #-attr      => {},
        );
    
    my $dbh = $mso->connect;
    
    # Table select (as array)
    my @result = $mso->table($sql, @inargs);

    # Table select (as hash)
    my %result = $mso->tableh($key, $sql, @inargs); # $key - primary index field name

    # Record (as array)
    my @result = $mso->record($sql, @inargs);

    # Record (as hash)
    my %result = $mso->recordh($sql, @inargs);

    # Fiels (as scalar)
    my $result = $mso->field($sql, @inargs);

    # SQL
    my $sth = $mso->execute($sql, @inargs);
    ...
    $sth->finish;

=head1 DESCRIPTION

For example: debug($oracle->field("select sysdate() from dual"));

=head1 AUTHOR

Serz Minus (Lepenkov Sergey) L<http://www.serzik.com> E<lt>minus@mail333.comE<gt>

=head1 COPYRIGHT

Copyright (C) 1998-2014 D&D Corporation. All Rights Reserved

=head1 LICENSE

This program is free software; you can redistribute it and/or modify it under the same terms and conditions as Perl itself.

This program is distributed under the GNU LGPL v3 (GNU Lesser General Public License version 3).

See C<LICENSE> file

=cut

use CTK::Util qw( :API );

use constant {
    WIN             => $^O =~ /mswin/i ? 1 : 0,
    TIMEOUT_CONNECT => 5,  # timeout connect
    TIMEOUT_REQUEST => 60, # timeout request
};

use vars qw/$VERSION/;
$VERSION = '2.25';

my $LOAD_SigAction = 0;
eval 'use Sys::SigAction';
my $es = $@;
if ($es) {
    eval '
        package # hide me from PAUSE
            Sys::SigAction;
        sub set_sig_handler($$;$$) { 1 };
        1;
    ';
    _error("Package Sys::SigAction don't installed! Please install this package") unless WIN;
} else {
    $LOAD_SigAction = 1;
}

use DBI();

sub new {
    my $class = shift;
    my @in = read_attributes([
          ['DSN','STRING','STR'],
          ['USER','USERNAME','LOGIN'],
          ['PASSWORD','PASS'],
          ['TIMEOUT_CONNECT','CONNECT_TIMEOUT','CNT_TIMEOUT','TIMEOUT_CNT','TO_CONNECT','CONNECT_TO'],
          ['TIMEOUT_REQUEST','REQUEST_TIMEOUT','REQ_TIMEOUT','TIMEOUT_REQ','TO_REQUEST','REQUEST_TO'],
          ['ATTRIBUTES','ATTR','ATTRHASH','PARAMS'],
        ],@_);

    # Îñíîâíûå àòðèáóòû ñîåäèíåíèÿ
    my %args = (
            dsn         => $in[0] || '',
            user        => $in[1] || '',
            password    => $in[2] || '',
            connect_to  => $in[3] || TIMEOUT_CONNECT,
            request_to  => $in[4] || TIMEOUT_REQUEST,
            attr        => $in[5] || undef,
            dbh         => undef,
        );
    
    # Èíèöèàëèçèðóåì ñîåäèíåíèå 
    $args{dbh} = DBI_CONNECT(@args{qw/dsn user password attr connect_to/});

    # Êîííåêò ÑÎÑÒÎßËÑß
    _debug("--- DBI CONNECT {".$args{dsn}."} ---");
   
    my $self = bless {%args}, $class;
    return $self;
}
sub connect {
    # Âîçâðàùàåì çàãîëâîê óêàçûâàþùèé íà îáúåêò ñîåäèíåíèÿ dbh
    my $self = shift;
    return $self->{dbh};
}
sub disconnect {
    # Ïðèíóäèòåëüíî ðàçðûâàåì ñâÿçü äî íàñòóïëåíèÿ DESTROY
    my $self = shift;
    DBI_DISCONNECT ($self->{dbh}) if $self->{dbh};
    # Äèñêîííåêò ÑÎÑÒÎßËÑß
    _debug("--- DBI DISCONNECT {".($self->{dsn} || '')."} ---"); # íà ìîìåíò äåñòðóêòóðà
}
sub field {
    my $self = shift;
    DBI_EXECUTE_FIELD($self->{dbh},$self->{request_to},@_)
}
sub record {
    my $self = shift;
    DBI_EXECUTE_RECORD($self->{dbh},$self->{request_to},@_)
}
sub recordh {
    my $self = shift;
    DBI_EXECUTE_RECORDH($self->{dbh},$self->{request_to},@_)
}
sub table {
    my $self = shift;
    DBI_EXECUTE_TABLE($self->{dbh},$self->{request_to},@_)
}
sub tableh {
    my $self = shift;
    my $key_field = shift; # Êëþ÷è êîíñòðóêòîðà (http://search.cpan.org/~timb/DBI-1.607/DBI.pm#fetchall_hashref)
    DBI_EXECUTE_TABLEH($self->{dbh},$key_field,$self->{request_to},@_)
}
sub execute {
    my $self = shift;
    DBI_EXECUTE($self->{dbh},$self->{request_to},@_)
}
sub DESTROY {
    my $self = shift;
    #debug ('-> Âûïîëíèëñÿ äåñòðóêòîð ñ îáúåêòîì: '.($self || ':('));
    $self->disconnect();
}
sub DBI_CONNECT {
    # Ñîåäèíåíèå ñ áàçîé äàííûõ DBI
    # $dbh = DBI_CONNECT($dsn, $user, $password, $attr)
    # IN:
    #   <DSN>      - DSN
    #   <USER>     - Èìÿ ïîëüçîâàòåëÿ ÁÄ
    #   <PASSWORD> - Ïàðîëü ïîëüçîâàòåëÿ ÁÄ
    #   <ATTR>     - Àòðèáóòû DBD::* (ññûëêà íà õåø, ñì. ìîäóëü äðàéâåðà)
    # OUT:
    #   $dbh - DataBase Handler Object
    #
    my $db_dsn      = shift || ''; # DSN
    my $db_user     = shift || ''; # Èìÿ ïîëüçîâàòåëÿ áàçû äàííûõ
    my $db_password = shift || ''; # ïàðîëü ïîëüçîâàòåëÿ áàçû äàííûõ
    my $db_attr     = shift || {}; # àòðèáóòû - íàïðèìåð {ORACLE_enable_utf8 => 1}
    my $db_tocnt    = shift || TIMEOUT_CONNECT; # Òàéìàóò äëÿ êîííåêòà

    my $dbh;
    
    my $count_connect     = 1;     # TRUE
    my $count_connect_msg = 'OK';  # TRUE
    eval {
        local $SIG{ALRM} = sub { die "Connecting timeout \"$db_dsn\"" } unless $LOAD_SigAction;
        my $h = Sys::SigAction::set_sig_handler( 'ALRM' ,sub { die "Connecting timeout \"$db_dsn\"" ; } );
        eval {
            alarm($db_tocnt); #implement 2 second time out
            unless ($dbh = DBI->connect($db_dsn, "$db_user", "$db_password", $db_attr)) {
                $count_connect     = 0; # FALSE
                $count_connect_msg = $DBI::errstr;
            }            
            alarm(0);
        };
        alarm(0);
        die $@ if $@;
    };
    if ( $@ ) {
        # Âñå ïëîõî
        $count_connect     = 0; # FALSE
        $count_connect_msg = $@;
    } 
    unless ($count_connect) {
        # Âñå ïëîõî :(
        _error("[".__PACKAGE__.": Connecting error \"$db_dsn\"] $count_connect_msg");
    }
  
    return $dbh;
}
sub DBI_DISCONNECT {
    # Çàêðûòèå ñîåäèíåíèÿ ñ áàçîé äàííûõ
    # $rc = DBI_DISCONNECT ($dbh)
    # IN:
    #   $dbh - DataBase Handler Object
    # OUT:
    #   $rc - îáúåêò ñîñòîÿíèÿ RC èëè 0 â ñëó÷àå íåóäà÷è
    #
    my $dbh = shift || return 0;
    my $rc = $dbh->disconnect;

    return $rc; 
}
sub DBI_EXECUTE_FIELD {
    # Ïîëó÷åíèå åäèíñòâåííîãî çíà÷åíèÿ (ïîëå)
    # $result = DBI_EXECUTE_FIELD($dbh, $sql, @inargs)
    # IN:
    #   $dbh - DataBase Handler Object
    #   $sql - SQL çàïðîñ
    #   [@inargs] - Àðãóìåíòû äëÿ áèíäèíãà
    # OUT:
    #   $result - Ïåðâûé [0] ìàññèâ ïðèíÿòûõ äàííûõ (ÍÅ ÑÑÛËÊÀ)

    my @result = DBI_EXECUTE_RECORD(@_);
    return $result[0] || '';
}
sub DBI_EXECUTE_RECORD {
    # Ïîëó÷åíèå ìíîæåñòâî çíà÷åíèé (ñòðîêó, çàïèñü)
    # @result = DBI_EXECUTE_RECORD($dbh, $sql, @inargs)
    # IN:
    #   $dbh - DataBase Handler Object
    #   $sql - SQL çàïðîñ
    #   [@inargs] - Àðãóìåíòû äëÿ áèíäèíãà
    # OUT:
    #   @result - ìàññèâ ïðèíÿòûõ äàííûõ (ÍÅ ÑÑÛËÊÀ)
    my $sth = DBI_EXECUTE(@_);
    return undef unless $sth;
    my @result = $sth->fetchrow_array;
    $sth->finish;
    return @result;
}
sub DBI_EXECUTE_RECORDH {
    # Ïîëó÷åíèå ìíîæåñòâî çíà÷åíèé (ñòðîêó, çàïèñü) â âèäå õýøà
    # %result = DBI_EXECUTE_RECORDH($dbh, $sql, @inargs)
    # IN:
    #   $dbh - DataBase Handler Object
    #   $sql - SQL çàïðîñ
    #   [@inargs] - Àðãóìåíòû äëÿ áèíäèíãà
    # OUT:
    #   %result - õåø ïðèíÿòûõ äàííûõ (ÍÅ ÑÑÛËÊÀ)
    my $sth = DBI_EXECUTE(@_);
    return undef unless $sth;
    my %result = %{$sth->fetchrow_hashref || {}};
    $sth->finish;
    return %result;
}
sub DBI_EXECUTE_TABLE {
    # Ïîëó÷åíèå âñåõ çíà÷åíèé (òàáëèöó à íå ññûëêó íà íå¸ êàê êàæåòñÿ íà ïåðâûé âçãëÿä)
    # @result = DBI_EXECUTE_TABLE($dbh, $sql, @inargs)
    # IN:
    #   $dbh - DataBase Handler Object
    #   $sql - SQL çàïðîñ
    #   [@inargs] - Àðãóìåíòû äëÿ áèíäèíãà
    # OUT:
    #   @result - ìàññèâ ïðèíÿòûõ äàííûõ (ÍÅ ÑÑÛËÊÀ)

    my $sth = DBI_EXECUTE(@_);
    return undef unless $sth;
    my @result = @{$sth->fetchall_arrayref};
    $sth->finish;
    # while (my @tbl_content=$sth->fetchrow_array) {push @result, [@tbl_content]} # Ñòàðûé ìåòîä. Íà âñÿêèé
    return @result;
}
sub DBI_EXECUTE_TABLEH {
    # Ïîëó÷åíèå âñåõ çíà÷åíèé (òàáëèöó à íå ññûëêó íà íå¸ êàê êàæåòñÿ íà ïåðâûé âçãëÿä)
    # %result = DBI_EXECUTE_TABLEH($dbh, $sql, @inargs)
    # IN:
    #   $dbh - DataBase Handler Object
    #   $key_field - Êëþ÷è êîíñòðóêòîðà (http://search.cpan.org/~timb/DBI-1.607/DBI.pm#fetchall_hashref)
    #   $sql - SQL çàïðîñ
    #   [@inargs] - Àðãóìåíòû äëÿ áèíäèíãà
    # OUT:
    #   Rresult - õåø õåøåé ïðèíÿòûõ äàííûõ (ÍÅ ÑÑÛËÊÀ)
    my $dbh       = shift;
    my $key_field = shift;

    my $sth = DBI_EXECUTE($dbh,@_);
    return undef unless $sth;
    my %result = %{$sth->fetchall_hashref($key_field) || {}};
    $sth->finish;
    return %result;
}
sub DBI_EXECUTE {
    # Âûïîëíåíèå çàïðîñà.
    # $sth = DBI_EXECUTE($dbh, $sql, @inargs)
    # IN:
    #   $dbh - DataBase Handler Object
    #   $tor - TimeOut of Request
    #   $sql - SQL çàïðîñ
    #   [@inargs] - Àðãóìåíòû äëÿ áèíäèíãà
    # OUT:
    #   $sth_ex - Îáúåêò âûïîëíåíèÿ äëÿ äàëüíåéøåãî ôèíèøèðîâàíèÿ ðåçóëüòàòà
    
    my $dbh = shift || return 0;
    my $tor = shift || TIMEOUT_REQUEST; # Òàéìàóò äëÿ âûïîëíåíèÿ çàïðîñà
    my $sql = shift || return 0;
 
    my @inargs = ();
    @inargs = @_ if exists $_[0];
    my $argb = "";
    $argb = "Params: ".join(", ", @inargs) if exists $inargs[0];
    
    my $sth_ex = $dbh->prepare($sql);
    unless ($sth_ex) {
        _error("[".__PACKAGE__.": Preparing error: $sql"."] ".$dbh->errstr);
        return undef;
    }
    
    my $count_execute     = 1;     # TRUE
    my $count_execute_msg = 'OK';  # TRUE
    eval {
        local $SIG{ALRM} = sub { die "Executing timeout" } unless $LOAD_SigAction;
        my $h = Sys::SigAction::set_sig_handler( 'ALRM' ,sub { die "Executing timeout" ; } );
        eval {
            alarm($tor);
            unless ($sth_ex->execute(@inargs)) {
                $count_execute     = 0; # FALSE
                $count_execute_msg = $dbh->errstr;  # FALSE
            }
            alarm(0);
        };
        alarm(0);
        die $@ if $@;
    };
    if ( $@ ) {
        $count_execute     = 0; # FALSE
        $count_execute_msg = $@;
    }
    unless ($count_execute) {
        # Âñå ïëîõî
        _error("[".__PACKAGE__.": Executing error: $sql".($argb?" / $argb":'')."] $count_execute_msg");
        return undef;
    }
    
    return $sth_ex || undef;
}
sub _debug { 1 }
sub _error { carp(@_) }
1;