package MySQL::Keeper; use strict; use warnings 'all'; use base qw(Contenido::Keeper); use locale; use Data::Dumper; use DBI; use DBD::mysql; use IO::File; use File::Copy; use Image::Size; use Contenido::Globals; use MySQL::Globals; # ------------------------------------------------------------------------------------------------ # Конструктор объекта базы данных. # Обязательный параметр - объект класса Contenido::State из которого # конструктор возьмет необходимые параметры для доступа к БД и т.д. # # Формат вызова: # MySQL::Keeper->new($state) # ------------------------------------------------------------------------------------------------ sub new { my ($proto, $state) = @_; die "Contenido Die: Неправильный вызов конструктора объекта базы данных. В параметрах нет объекта класса MySQL::State\n" unless ref($mstate); my $class = ref($proto) || $proto; my $self = {}; bless($self, $class); # Заполним собственные свойства конкретными данными... $self->{db_type} = 'remote'; $self->{db_keepalive} = 1; $self->{db_host} = $state->db_host(); $self->{db_name} = $state->db_name(); $self->{db_user} = $state->db_user(); $self->{db_password} = $state->db_password(); $self->{db_port} = $state->db_port(); # $self->{data_dir} = $self->{data_directory} = $state->data_directory(); # $self->{images_dir} = $self->{images_directory} = $state->images_directory(); # $self->{binary_dir} = $self->{binary_directory} = $state->binary_directory(); # $self->{debug} = $state->debug(); # $self->{store_method} = $state->store_method(); # $self->{cascade} = $state->cascade(); $self->{state} = $state; $self->_init_(); # соединяемся с базой если используется постоянное соединение $self->connect(); return $self; } # МЕТОДЫ ДОСТУПА К СОЕДИНЕНИЯМ С БАЗОЙ УМНЫЕ # получение соединения с базой или установка нового если его не было sub SQL { my $self = shift; return ($self->connect_check() ? $self->{SQL} : undef); } # ------------------------------------------------------------------------------------------------- # Открываем соединение с базой данных # ------------------------------------------------------------------------------------------------- sub connect { my $self = shift; #соединение уже есть if ($self->is_connected) { } else { unless ($self->{SQL} = $self->db_connect) { warn "Не могу соединиться с базой данных"; die; } $self->{SQL}->do("SET NAMES '".$self->state->db_client_encoding."'") if ($self->state->db_client_encoding); } $self->{_connect_ok} = 1; return 1; } #проверка соединения с базой кеширующая состояние соединения sub connect_check { my $self = shift; return 1 if ($self->{_connect_ok}); if ($self->is_connected) { $self->{_connect_ok} = 1; return 1; } else { if ($self->connect) { return 1; } else { #сюда по логике попадать не должно так как die вылететь должен warn "Connect failed\n"; return 0; } } } sub db_connect { my $self = shift; my $dbh = DBI->connect("DBI:mysql:database=".$self->{db_name}.";host=".$self->{db_host}, $self->{db_user}, $self->{db_password}) || die "Contenido Error: Не могу соединиться с MySQL базой данных\n"; $dbh->{'AutoCommit'} = 1; $dbh->{mysql_auto_reconnect} = 1; return $dbh; } sub is_connected { my $self = shift; if (ref($self->{SQL}) and $self->{SQL}->can('ping') and $self->{SQL}->ping()) { $self->{_connect_ok} = 1; return 1; } else { $self->{_connect_ok} = 0; return 0; } # warn 'Check if MySQL DB connected: '.(ref $self && exists $self->{SQL} && ref $self->{SQL} ? 1 : 0 ) if $DEBUG; # return ( ref($self) && exists $self->{SQL} && ref $self->{SQL} ); } # ------------------------------------------------------------------------------------------------- # Закрываем соединение с базой данных # ------------------------------------------------------------------------------------------------- sub shutdown { my $self = shift; $self->{SQL}->disconnect() if ref $self->{SQL}; delete $self->{SQL}; warn "SQL= ".(exists $self->{SQL} && ref $self->{SQL} ? 1 : 0) if $DEBUG; warn "Contenido Debug: Закрыто соединение с базой данных MySQL на порту ".$self->{db_port}." keepalive=".$mstate->db_keepalive." .\n" if ($self->{debug}); } # ---------------------------------------------------------------------------- # Инициализация. # - Создает внутри объекта хэш с типами полей - это нужно для быстрой # работы метода AUTOLOAD... # ---------------------------------------------------------------------------- sub _init_ { my $self = shift; foreach my $attribute ( qw( db_host db_name db_user db_password db_port db_type db_keepalive default_status debug state SQL) ) { $self->{attributes}->{ $attribute } = 'SCALAR'; } } # ---------------------------------------------------------------------------- # Функции работы с данными: # # >> get_object # table => название таблицы # fields => [fieldname, fieldname, ..., fieldname] # wheres => список ограничений # wheres => 'condition' # wheres => ['condition 1', 'condition 2'] # wheres => { field1 => 'value1', field2 => 'value2' } # values => список значений для wheres с передачей списка # limit => Limit # offset => Offset # order_by=> Сортировка # id => По ID # count => Just count # # ---------------------------------------------------------------------------- sub get_object { my $self = shift; my %opts = @_; return undef unless $opts{table}; my $ret_value = []; my $request = "SELECT "; if ( exists $opts{count} && $opts{count} ) { $request .= 'COUNT(*) AS cid '; } else { $request .= ( exists $opts{fields} && ref $opts{fields} eq 'ARRAY' ? join(', ',@{$opts{fields}}) : '*' ); } my (@wheres, @values); $request .= " FROM $opts{table}"; if ( exists $opts{id} && $opts{id} ) { if (ref $opts{id} eq 'ARRAY') { @wheres = ( "id IN (".join(',', map { '?' } @{$opts{id}} ).")" ); @values = @{ $opts{id} }; } else { @wheres = ( "id = ?" ); @values = ( $opts{id} ); } } if ( exists $opts{wheres} && $opts{wheres} ) { if ( ref $opts{wheres} eq 'ARRAY' ) { push @wheres, @{ $opts{wheres} }; if ( exists $opts{values} && $opts{values} ) { if ( exists $opts{values} && ref $opts{values} eq 'ARRAY' && @{ $opts{values} } ) { push @values, @{ $opts{values} }; } elsif ( !ref $opts{values} ) { push @values, $opts{values}; } } } elsif ( ref $opts{wheres} eq 'HASH' ) { while ( my ($field, $value) = each %{ $opts{wheres} } ) { push @wheres, "$field = ?"; push @values, $value; } } else { push @wheres, $opts{wheres}; if ( exists $opts{values} && $opts{values} ) { if ( exists $opts{values} && ref $opts{values} eq 'ARRAY' && @{ $opts{values} } ) { push @values, @{ $opts{values} }; } elsif ( !ref $opts{values} ) { push @values, $opts{values}; } } } } if ( @wheres ) { $request .= ' WHERE '.join( ' and ', map { "($_)" } @wheres ); } if ( $opts{order_by} ) { $request .= " ORDER BY $opts{order_by}"; } if ( $opts{limit} ) { $request .= " LIMIT $opts{limit}"; } if ( $opts{offset} ) { $request .= " OFFSET $opts{offset}"; } warn $request if $DEBUG; warn "Values: [".join(',', map { defined $_ ? $_ : 'NULL' } @values )."]\n" if $DEBUG && @values; my $dbh = $self->SQL; my $result = $dbh->prepare($request); my $res = $result->execute( @values ); unless ( $res ) { warn $dbh->errstr; warn "QUERY: [$request]\n"; warn "OPTS: [".Dumper(\%opts)."]\n"; return undef; } if ( $opts{count} ) { my $ln = $result->fetchrow_hashref(); return $ln->{cid}; } while ( my $ln = $result->fetchrow_hashref() ) { push @$ret_value, $ln; } if ( $opts{id} && ref $opts{id} ne 'ARRAY' && ref $ret_value eq 'ARRAY' && scalar @$ret_value) { $ret_value = $ret_value->[0]; } elsif ( $opts{id} && ref $opts{id} eq 'ARRAY' && ref $ret_value eq 'ARRAY' && scalar @$ret_value) { } elsif ( $opts{id} ) { return undef; } elsif ( ref $ret_value ne 'ARRAY' ) { return undef; } $result->finish; return $ret_value; } # --------------------------------------------------------------------------- # Получить следующий ID в объекте # >> get_next_id # table => название таблицы # # ---------------------------------------------------------------------------- sub get_next_id { my $self = shift; my $tablename = shift; return unless $tablename; my $request = "SELECT MAX(id)+1 FROM $tablename"; my $dbh = $self->SQL; my $result = $dbh->prepare($request); $result->execute(); my $ln = $result->fetchrow(); $result->finish; return $ln; } # --------------------------------------------------------------------------- # Сохранить Объект: # store_object( 'tablename', key => 'value', key => 'value' ) # object => { # key => 'value', # } # ---------------------------------------------------------------------------- sub store_object { my $self = shift; my $tablename = shift; return unless $tablename; my %object = @_; my $request; my @values; my $id = 0; if ( exists $object{id} && $object{id} ) { $id = $object{id}; $request = "UPDATE $tablename SET " ; my $i = 0; foreach my $field ( keys %object ) { next if $field eq 'id'; $request .= ',' if $i++; $request .= " $field = ?"; push @values, $object{$field}; } $request .= ' WHERE id='.$object{id}; } else { $id = $self->get_next_id($tablename); $request = "INSERT INTO $tablename " ; my @keys = grep { $_ ne 'id' } keys %object; @values = map { $object{$_} } @keys; unshift @keys, 'id'; unshift @values, $id; $request .= " (".join(', ', @keys).") "; $request .= " VALUES (".join(', ', map { '?' } @keys).")"; } warn $request."\n" if $DEBUG; warn "Values: [".join(',',@values)."]\n" if $DEBUG; my $dbh = $self->SQL; my $result = $dbh->prepare($request); my $res = $result->execute(@values); unless ( $res ) { warn "OBJECT STORE ERROR: [".$result->errstr."]\n"; } $result->finish; return $res ? $id : undef; } # --------------------------------------------------------------------------- # Сохранить Объект с автоинкрементом: # # store_object_autoinc( 'tablename', key => 'value', key => 'value' ) # object => { # key => 'value', # } # # ---------------------------------------------------------------------------- sub store_object_autoinc { my $self = shift; my $tablename = shift; return unless $tablename; my (%object) = @_; my $request; my @values; my $id = 0; if ( exists $object{id} && $object{id} ) { $id = $object{id}; $request = "UPDATE $tablename SET " ; my $i = 0; foreach my $field ( keys %object ) { next if $field eq 'id'; $request .= ',' if $i++; $request .= " $field = ?"; push @values, $object{$field}; } $request .= ' WHERE id='.$object{id}; } else { $request = "INSERT INTO $tablename " ; my @keys = grep { $_ ne 'id' } keys %object; @values = map { $object{$_} } @keys; $request .= " (".join(', ', @keys).") "; $request .= " VALUES (".join(', ', map { '?' } @keys).")"; } warn $request."\n" if $DEBUG; warn "Values: [".join(',', map { !defined $_ ? 'NULL' : $_ } @values)."]\n" if $DEBUG; my $dbh = $self->SQL; my $result = $dbh->prepare($request); my $res = $result->execute(@values); unless ( $res ) { warn "OBJECT STORE ERROR: [".$result->errstr."]\n"; } unless ( $id ) { $id = $dbh->last_insert_id(undef, undef, undef, undef); warn "INSERT TRACK. ID = [$id]\n"; } $result->finish; return $res ? $id : undef; } # --------------------------------------------------------------------------- # Удалить Объект: # # ---------------------------------------------------------------------------- sub delete_object { my $self = shift; my $tablename = shift; return unless $tablename; my %opts = @_; my $request; if ( exists $opts{id} && $opts{id} ) { $request = "DELETE FROM $tablename WHERE id = ?"; } warn $request if $DEBUG; my $dbh = $self->SQL; my $result = $dbh->prepare($request); $result->execute($opts{id}); $result->finish; } 1;