package NGCP::BulkProcessor::SqlConnector; use strict; ## no critic use threads; use threads::shared; use NGCP::BulkProcessor::Globals qw( $enablemultithreading $is_perl_debug ); use NGCP::BulkProcessor::Logging qw( getlogger dbdebug dbinfo); use NGCP::BulkProcessor::LogError qw( dberror dbwarn notimplementederror sortconfigerror); use DBI; use NGCP::BulkProcessor::Utils qw(threadid); use NGCP::BulkProcessor::Array qw(arrayeq); use NGCP::BulkProcessor::RandomString qw(createtmpstring); use NGCP::BulkProcessor::Calendar qw(); require Exporter; our @ISA = qw(Exporter); our @EXPORT_OK = qw(get_tableidentifier $log_db_operations); #my $logger = getlogger(__PACKAGE__); our $log_db_operations = 0; #0; my $temptable_randomstringlength = 4; sub new { my $class = shift; my $self = bless {}, $class; my $instanceid = shift; my $cluster = shift; $self->{drh} = undef; $self->{dbh} = undef; $self->{instanceid} = $instanceid; $self->{tid} = threadid(); $self->{sth} = undef; $self->{query} = undef; $self->{params} = undef; $self->{temp_tables} = []; $self->{cluster} = $cluster; return $self; } sub set_transaction_isolation { my ($self,$level) = @_; notimplementederror((ref $self) . ': ' . (caller(0))[3] . ' not implemented',getlogger(__PACKAGE__)); return undef; } sub _gettemptablename { my $self = shift; my $temp_tablename = 'TMP_TBL_' . $self->{tid} . '_'; if (length($self->{instanceid}) > 0) { $temp_tablename .= $self->{instanceid} . '_'; } $temp_tablename .= createtmpstring($temptable_randomstringlength); #$self->{temp_table_count}; return $temp_tablename; } sub instanceidentifier { my $self = shift; $self->{instanceid} = shift if @_; return $self->{instanceid}; } sub cluster { my $self = shift; $self->{cluster} = shift if @_; return $self->{cluster}; } sub _connectidentifier { my $self = shift; notimplementederror((ref $self) . ': ' . (caller(0))[3] . ' not implemented',getlogger(__PACKAGE__)); return undef; } sub connectidentifier { my $self = shift; my $cluster = $self->{cluster}; if (defined $cluster) { return $cluster->{name}; } else { $self->_connectidentifier(); } } sub tableidentifier { my $self = shift; my $tablename = shift; my (@params) = @_; notimplementederror((ref $self) . ': ' . (caller(0))[3] . ' not implemented',getlogger(__PACKAGE__)); return undef; } sub columnidentifier { my $self = shift; my $columnname = shift; my (@params) = @_; return join('.',map { $self->_columnidentifier($_,@params); } split(/\./,$columnname,-1)); } sub _columnidentifier { my $self = shift; my $columnname = shift; my (@params) = @_; notimplementederror((ref $self) . ': ' . (caller(0))[3] . ' not implemented',getlogger(__PACKAGE__)); return undef; } sub get_tableidentifier { my ($tablename,@params) = @_; notimplementederror(__PACKAGE__ . ': ' . (caller(0))[3] . ' not implemented',getlogger(__PACKAGE__)); return undef; } sub getsafetablename { # make a table name (identifier) string save for use within create table statements # of this rdbms connector. my $self = shift; my ($tableidentifier) = @_; #shift; $tableidentifier =~ s/[^0-9a-z_]/_/gi; return $tableidentifier; } sub _extract_indexcols { my $self = shift; my $indexcols = shift; if (defined $indexcols and ref $indexcols eq 'ARRAY') { my @blankcols = map { local $_ = $_; s/\s*\(\d+\).*$//g; $_; } @$indexcols; return \@blankcols; } else { return []; } } sub vacuum { my $self = shift; my $tablename = shift; notimplementederror((ref $self) . ': ' . (caller(0))[3] . ' not implemented',getlogger(__PACKAGE__)); } sub paginate_sort_query { my $self = shift; my $statement = shift; my $offset = shift; my $limit = shift; my $sortingconfigurations = shift; if ($statement =~ /limit\s+\d+(,\s*\d+)?\s*$/i) { $statement = "SELECT * FROM ($statement) AS _ps"; } my $orderby = $self->_orderby_columns($sortingconfigurations); if (length($orderby) > 0) { $statement .= ' ORDER BY ' . $orderby; } if (defined $offset and defined $limit) { $statement .= ' LIMIT ' . $offset . ', ' . $limit; } return $statement; } sub insert_ignore_phrase { my $self = shift; notimplementederror((ref $self) . ': ' . (caller(0))[3] . ' not implemented',getlogger(__PACKAGE__)); } sub _force_numeric_column { my $self = shift; my $column = shift; notimplementederror((ref $self) . ': ' . (caller(0))[3] . ' not implemented',getlogger(__PACKAGE__)); } sub _orderby_columns { my $self = shift; my $sortingconfigurations = shift; my @orderby = (); if (defined $sortingconfigurations) { foreach my $sc (@$sortingconfigurations) { if (defined $sc and ref $sc eq 'HASH') { my $columnname = ((exists $sc->{memberchain}) ? $sc->{memberchain} : $sc->{column}); if (ref $columnname eq 'ARRAY') { $columnname = $columnname->[0]; } if (length($columnname) > 0) { $columnname = $self->columnidentifier($columnname); my $orderby_column; if ($sc->{numeric}) { $orderby_column = $self->_force_numeric_column($columnname); } else { $orderby_column = $columnname; } if (!defined $sc->{dir} or $sc->{dir} > 0) { $orderby_column .= ' ASC'; } else { $orderby_column .= ' DESC'; } push(@orderby,$orderby_column); } else { sortconfigerror(undef,'sort column required',getlogger(__PACKAGE__)); } } else { sortconfigerror(undef,'invalid sorting configuration',getlogger(__PACKAGE__)); } } } return join(', ',@orderby); } sub getdatabases { my $self = shift; notimplementederror((ref $self) . ': ' . (caller(0))[3] . ' not implemented',getlogger(__PACKAGE__)); return []; } sub _createdatabase { my $self = shift; my ($databasename) = @_; notimplementederror((ref $self) . ': ' . (caller(0))[3] . ' not implemented',getlogger(__PACKAGE__)); return []; } sub db_connect { my $self = shift; my (@params) = @_; if (defined $self->{dbh}) { $self->_db_disconnect(); } # child class will do the connect stuff... } sub db_disconnect { my $self = shift; #my $tid = threadid(); my $cluster = $self->{cluster}; if (defined $cluster) { dbdebug($self,'disconnecting database cluster ' . $cluster->{name},getlogger(__PACKAGE__)); foreach my $node (@{$cluster->{nodes}}) { if ($node->{active}) { my $node_db = &{$node->{get_db}}($self->{instanceid},0); $node_db->_db_disconnect(); } } #$cluster->{scheduling_vars} = {}; } else { $self->_db_disconnect(); } } sub _db_disconnect { my $self = shift; # since this is also called from DESTROY, no die() here! $self->db_finish(); if (defined $self->{dbh}) { #cleartableinfo($self); #dbdebug($self,'disconnecting' . ((defined $self->{cluster}) ? ' ' . $self->_connectidentifier() : ''),getlogger(__PACKAGE__)); dbdebug($self,'disconnecting',getlogger(__PACKAGE__)); foreach my $temp_tablename (@{$self->{temp_tables}}) { #if ($self->table_exists($temp_tablename)) { $self->drop_table($temp_tablename); #} } $self->{temp_tables} = []; $self->{dbh}->disconnect() or dbwarn($self,'error disconnecting: ' . $self->{dbh}->errstr(),getlogger(__PACKAGE__)); $self->{dbh} = undef; dbinfo($self,'disconnected',getlogger(__PACKAGE__)); #dbinfo($self,((defined $self->{cluster}) ? $self->_connectidentifier() . ' ' : '') . 'disconnected',getlogger(__PACKAGE__)); } # further disconnect code follows in child classes.... } sub is_connected { my $self = shift; return (defined $self->{dbh}); } sub getfieldnames { my $self = shift; my $tablename = shift; notimplementederror((ref $self) . ': ' . (caller(0))[3] . ' not implemented',getlogger(__PACKAGE__)); return []; } sub getprimarykeycols { my $self = shift; my $tablename = shift; notimplementederror((ref $self) . ': ' . (caller(0))[3] . ' not implemented',getlogger(__PACKAGE__)); return []; } sub create_temptable { my $self = shift; my $select_stmt = shift; my $indexes = shift; notimplementederror((ref $self) . ': ' . (caller(0))[3] . ' not implemented',getlogger(__PACKAGE__)); return ''; } sub create_primarykey { my $self = shift; my ($tablename,$keycols,$fieldnames) = @_; notimplementederror((ref $self) . ': ' . (caller(0))[3] . ' not implemented',getlogger(__PACKAGE__)); return 0; } sub create_indexes { my $self = shift; my ($tablename,$indexes,$keycols) = @_; notimplementederror((ref $self) . ': ' . (caller(0))[3] . ' not implemented',getlogger(__PACKAGE__)); return 0; } sub create_texttable { my $self = shift; my ($tablename,$fieldnames,$keycols,$indexes,$truncate,$defer_indexes) = @_; notimplementederror((ref $self) . ': ' . (caller(0))[3] . ' not implemented',getlogger(__PACKAGE__)); return 0; } sub truncate_table { my $self = shift; my $tablename = shift; notimplementederror((ref $self) . ': ' . (caller(0))[3] . ' not implemented',getlogger(__PACKAGE__)); } sub table_exists { my $self = shift; my $tablename = shift; notimplementederror((ref $self) . ': ' . (caller(0))[3] . ' not implemented',getlogger(__PACKAGE__)); return 0; } sub drop_table { my $self = shift; my $tablename = shift; notimplementederror((ref $self) . ': ' . (caller(0))[3] . ' not implemented',getlogger(__PACKAGE__)); } sub _prepare_error { my $self = shift; my $query = shift; dberror($self,"failed to prepare:\n" . $query . "\nDBI error:\n" . $self->{dbh}->errstr(),getlogger(__PACKAGE__)); } sub _execute_error { my $self = shift; my $query = shift; my $sth = shift; my $errstr; if (defined $sth) { $errstr = $sth->errstr(); } else { $errstr = $self->{dbh}->errstr(); } dberror($self,"failed to execute:\n" . $query . "\nparameters:\n". join(', ', @_) . "\nDBI error:\n" . $errstr,getlogger(__PACKAGE__)); } sub _fetch_error { my $self = shift; my $query = shift; my $sth = shift; my $operation = shift; my $index = shift; my $errstr; if (defined $sth) { $errstr = $sth->errstr(); } else { $errstr = $self->{dbh}->errstr(); } dberror($self,'failed with ' . $operation . ":\n" . $query . "\n" . ((defined $index) ? 'column index: ' . $index . "\n" : '') . "parameters:\n". join(', ', @_) . "\nDBI error:\n" . $errstr,getlogger(__PACKAGE__)); } sub datetime_to_string { my $self = shift; my ($dt) = @_; return NGCP::BulkProcessor::Calendar::datetime_to_string($dt); } sub datetime_from_string { my $self = shift; my ($s,$tz) = @_; return NGCP::BulkProcessor::Calendar::datetime_from_string($s,$tz); } # "The data type is 'sticky' in that bind values passed to execute() are bound with # the data type specified by earlier bind_param() calls, if any." sub _bind_params { my $self = shift; my $sth = shift; my @params = (); my $p_num = 1; foreach my $param (@_) { if (defined $param and 'HASH' eq $param) { push(@params, delete $param->{value}); $sth->bind_param($p_num, undef, $param); } else { push(@params,$param); } $p_num++; } return @params; } #sub db_autocommit { # # my $self = shift; # if (defined $self->{dbh}) { # if (@_) { # my ($autocommit) = @_; # $autocommit = ($autocommit ? 1 : 0); # dbdebug($self,'set AutoCommit ' . $self->{dbh}->{AutoCommit} . ' -> ' . $autocommit,getlogger(__PACKAGE__)); # $self->{dbh}->{AutoCommit} = $autocommit; # } # return $self->{dbh}->{AutoCommit}; # } # return undef; # #} # This method executes a SQL query that doesn't return any data. The # query may contain placeholders, that will be replaced by the elements # in @params during execute(). The method will die if any error occurs # and return whatever DBI's execute() returned. sub db_do { my $self = shift; my $query = shift; my $result = 0; if (defined $self->{dbh}) { my $sth = $self->{dbh}->prepare($query) or $self->_prepare_error($query); my @params = $self->_bind_params($sth,@_); dbdebug($self,'db_do: ' . $query . "\nparameters:\n" . join(', ', @params),getlogger(__PACKAGE__)) if $log_db_operations; $result = $sth->execute(@params); if (defined $result) { if ('0E0' eq $result) { return 0; } } else { $self->_execute_error($query,$sth,@params); } } return $result; } # get the first value of the first row of data that is returned from the # database. Returns undef if no data is found. sub db_get_value { my $self = shift; my $query = shift; my $row = undef; if (defined $self->{dbh}) { my $sth = $self->{dbh}->prepare($query) or $self->_prepare_error($query); my @params = $self->_bind_params($sth,@_); dbdebug($self,'db_get_value: ' . $query . "\nparameters:\n" . join(', ', @params),getlogger(__PACKAGE__)) if $log_db_operations; $sth->execute(@params) or $self->_execute_error($query,$sth,@params); $row = $sth->fetchrow_arrayref(); $self->_fetch_error($query,$sth,'fetchrow_arrayref',undef,@params) if !defined $row and $sth->err(); $sth->finish(); } return ((defined $row) ? $$row[0] : undef); } # get a reference to the first row of data that is returned from the database. # (I.e. whatever is returned by DBI's fetchrow_hashref().) sub db_get_row { my $self = shift; my $query = shift; my $row = []; if (defined $self->{dbh}) { my $sth = $self->{dbh}->prepare($query) or $self->_prepare_error($query); my @params = $self->_bind_params($sth,@_); dbdebug($self,'db_get_row: ' . $query . "\nparameters:\n" . join(', ', @params),getlogger(__PACKAGE__)) if $log_db_operations; $sth->execute(@params) or $self->_execute_error($query,$sth,@params); $row = $sth->fetchrow_hashref(); $self->_fetch_error($query,$sth,'fetchrow_hashref',undef,@params) if !defined $row and $sth->err(); $sth->finish(); } return $row; } # get a reference to an array containing the first value of every data row that # is returned from the database like DBI's selectcol_arrayref() does. sub db_get_col { my $self = shift; my $query = shift; my $col = []; if (defined $self->{dbh}) { my $sth = $self->{dbh}->prepare($query) or $self->_prepare_error($query); my @params = $self->_bind_params($sth,@_); dbdebug($self,'db_get_col: ' . $query . "\nparameters:\n" . join(', ', @params),getlogger(__PACKAGE__)) if $log_db_operations; $col = $self->{dbh}->selectcol_arrayref($sth, undef, @params); #die "Failed to selectcol_arrayref:\n$query\nDBI error:". $sth->errstr() if !defined $col and $sth->err(); $self->_fetch_error($query,$sth,'selectcol_arrayref',undef,@params) if !defined $col and $sth->err(); $sth->finish(); } return $col; } # get all data that is returned from the database. (I.e. a reference to an # array containing entries returned by DBI's fetchrow_hashref().) sub db_get_all_arrayref { my $self = shift; my $query = shift; my @rows = (); if (defined $self->{dbh}) { my $sth = $self->{dbh}->prepare($query) or $self->_prepare_error($query); my @params = $self->_bind_params($sth,@_); dbdebug($self,'db_get_all_arrayref: ' . $query . "\nparameters:\n" . join(', ', @params),getlogger(__PACKAGE__)) if $log_db_operations; $sth->execute(@params) or $self->_execute_error($query,$sth,@params); while (my $row = $sth->fetchrow_hashref()) { $self->_fetch_error($query,$sth,'fetchrow_hashref',undef,@params) if $sth->err(); push @rows, $row; } $sth->finish(); } return \@rows; } # get a reference to a hash containing a hash reference for each row, like # DBI's fetchall_hashref() does. sub db_get_all_hashref { my $self = shift; my $query = shift; my $index = shift; my $result = {}; if (defined $self->{dbh}) { my $sth = $self->{dbh}->prepare($query) or $self->_prepare_error($query); my @params = $self->_bind_params($sth,@_); dbdebug($self,'db_get_all_hashref: ' . $query . "\nparameters:\n" . join(', ', @params),getlogger(__PACKAGE__)) if $log_db_operations; $sth->execute(@params) or $self->_execute_error($query,$sth,@params); $result = $sth->fetchall_hashref($index); $self->_fetch_error($query,$sth,'fetchall_hashref',$index,@params) if $sth->err(); $sth->finish(); } return $result; } # get a reference to a hash that is composed of the key_column as keys and the # value_column as values. sub db_get_mapref { my $self = shift; my $query = shift; my $index = shift; my $value = shift; my $result = {}; if (defined $self->{dbh}) { my $sth = $self->{dbh}->prepare($query) or $self->_prepare_error($query); my @params = $self->_bind_params($sth,@_); dbdebug($self,'db_get_mapref: ' . $query . "\nparameters:\n" . join(', ', @params),getlogger(__PACKAGE__)) if $log_db_operations; $sth->execute(@_) or $self->_execute_error($query,$sth,@params); my $rows = $sth->fetchall_hashref($index); #die "Failed to fetchall_hashref:\n$query\nDBI error:". $sth->errstr() if $sth->err(); $self->_fetch_error($query,$sth,'fetchall_hashref',$index,@params) if $sth->err(); foreach my $key (keys %$rows) { $result->{$key} = $$rows{$key}{$value}; } $sth->finish(); return $result; } return $result; } sub db_begin { my $self = shift; if (defined $self->{dbh}) { dbdebug($self,'db_begin',getlogger(__PACKAGE__)); $self->{dbh}->begin_work() or dberror($self, "failed with begin_transaction \nDBI error:\n" . $self->{dbh}->errstr(),getlogger(__PACKAGE__)); if ($self->{dbh}->{AutoCommit}) { dbwarn($self,'autocommit was not disabled',getlogger(__PACKAGE__)); } } } sub db_commit { my $self = shift; if (defined $self->{dbh}) { dbdebug($self,'db_commit',getlogger(__PACKAGE__)); if ($is_perl_debug) { #https://rt.cpan.org/Public/Bug/Display.html?id=102791 # no context: $self->{dbh}->commit(); if ($DBI::err) { dberror($self, "failed to commit changes\nDBI error:\n" . $self->{dbh}->errstr(),getlogger(__PACKAGE__)); } } else { #my @wa = $self->{dbh}->commit() or dberror($self, "failed to commit changes\nDBI error:\n" . $self->{dbh}->errstr(),getlogger(__PACKAGE__)); } } } sub db_rollback { my $self = shift; my ($log) = @_; if (defined $self->{dbh}) { dbdebug($self,'db_rollback',getlogger(__PACKAGE__)); if ($is_perl_debug) { $self->{dbh}->rollback(); if ($DBI::err) { dberror($self, "failed to rollback changes\nDBI error:\n" . $self->{dbh}->errstr(),getlogger(__PACKAGE__)); } } else { $self->{dbh}->rollback() or dberror($self, "failed to rollback changes\nDBI error:\n" . $self->{dbh}->errstr(),getlogger(__PACKAGE__)); } dbinfo($self,'transaction rolled back',getlogger(__PACKAGE__)) if $log; } } sub db_quote { my $self = shift; my $value = shift; my $result = $value; if (defined $self->{dbh}) { $result = $self->{dbh}->quote($value) or dberror($self, "failed to quote value\nDBI error:\n" . $self->{dbh}->errstr(),getlogger(__PACKAGE__)); } return $result; } sub db_last_insert_id { my $self = shift; notimplementederror((ref $self) . ': ' . (caller(0))[3] . ' not implemented',getlogger(__PACKAGE__)); } sub DESTROY { my $self = shift; # perl threads works like a fork, each thread owns a shalow? copy # of the entire current context, at the moment it starts. # due to this, if the thread is finished, perl gc will invoke destructors # on the thread's scope elements, that potentially contains connectors from # the main tread. it will actually attempt destroy them (disconnect, etc.) # this is a problem with destructors that change object state like this one # # to avoid this, we perform destruction tasks only if the destructing tid # is the same as the creating one: if ($self->{tid} == threadid()) { $self->_db_disconnect(); delete $self->{drh}; eval { dbdebug($self,(ref $self) . ' connector destroyed',getlogger(__PACKAGE__)); }; #} else { # print "NOT destroyed\n"; } } sub lock_tables { my $self = shift; my $tablestolock = shift; #$self->db_begin(); notimplementederror((ref $self) . ': ' . (caller(0))[3] . ' not implemented',getlogger(__PACKAGE__)); } sub unlock_tables { my $self = shift; #$self->db_commit(); notimplementederror((ref $self) . ': ' . (caller(0))[3] . ' not implemented',getlogger(__PACKAGE__)); } sub db_do_begin { my $self = shift; my $query = shift; #my $tablename = shift; my $transactional = shift; #notimplementederror('db_do_begin',getlogger(__PACKAGE__)); if (defined $self->{dbh} and !defined $self->{sth}) { # and length($tablename) > 0) { dbdebug($self,'db_do_begin: ' . $query,getlogger(__PACKAGE__)); if ($transactional) { #$self->lock_tables({ $tablename => 'WRITE' }); $self->db_begin(); } $self->{sth} = $self->{dbh}->prepare($query) or $self->_prepare_error($query); $self->{query} = $query; $self->{params} = []; } } sub db_do_rowblock { my $self = shift; my $rows = shift; #notimplementederror('db_do_rowblock',getlogger(__PACKAGE__)); if (defined $self->{dbh} and defined $self->{sth} and defined $rows and ref $rows eq 'ARRAY') { #dberror($self,'test error',getlogger(__PACKAGE__)); #mysqldbdebug($self,"db_do_rowblock\nrows:\n" . (scalar @$rows),getlogger(__PACKAGE__)); #mysqldbdebug($self,'db_do_rowblock: ' . $self->{query} . "\nparameters:\n" . join(', ', @_),getlogger(__PACKAGE__)); foreach my $row (@$rows) { my @params = $self->_bind_params($self->{sth},@$row); dbdebug($self,'db_do_rowblock: ' . $self->{query} . "\nparameters:\n" . join(', ', @params),getlogger(__PACKAGE__)) if $log_db_operations; $self->{sth}->execute(@params) or $self->_execute_error($self->{query},$self->{sth},@params); $self->{params} = \@params; } } } sub db_get_begin { my $self = shift; my $query = shift; #my $tablename = shift; my $transactional = shift; if (defined $self->{dbh} and !defined $self->{sth}) { # and length($tablename) > 0) { #eval { $self->lock_tables({ $tablename => 'WRITE' }); }; if ($transactional) { #$self->lock_tables({ $tablename => 'WRITE' }); $self->db_begin(); } else { my $offset = shift; my $limit = shift; $query = $self->paginate_sort_query($query,$offset,$limit,undef); } $self->{sth} = $self->{dbh}->prepare($query) or $self->_prepare_error($query); my @params = $self->_bind_params($self->{sth},@_); dbdebug($self,'db_get_begin: ' . $query . "\nparameters:\n" . join(', ', @params),getlogger(__PACKAGE__)); $self->{sth}->execute(@params) or $self->_execute_error($query,$self->{sth},@params); $self->{query} = $query; $self->{params} = \@params; } } sub multithreading_supported { my $self = shift; return 0; } sub rowblock_transactional { my $self = shift; return 0; } sub db_get_rowblock { my $self = shift; my $max_rows = shift; #https://www.perlmonks.org/?node_id=273952 if ($enablemultithreading) { #my $rows : shared = []; my @rows :shared = (); #my $rows = &share([]); # beware of '&' here!!!! #my $rows = shared_clone({}); if (defined $self->{dbh} and defined $self->{sth}) { dbdebug($self,'db_get_rowblock: ' . $self->{query} . "\nparameters:\n" . join(', ', @{$self->{params}}),getlogger(__PACKAGE__)) if $log_db_operations; foreach (@{$self->{sth}->fetchall_arrayref(undef, $max_rows)}) { my @row : shared = @{$_}; push @rows, \@row; } $self->_fetch_error($self->{query},$self->{sth},'db_get_rowblock',undef,@{$self->{params}}) if $self->{sth}->err(); } #share(@rows); return \@rows; #return $rows; #return \@rows; } else { my $rows = []; if (defined $self->{dbh} and defined $self->{sth}) { dbdebug($self,'db_get_rowblock: ' . $self->{query} . "\nparameters:\n" . join(', ', @{$self->{params}}),getlogger(__PACKAGE__)) if $log_db_operations; $rows = $self->{sth}->fetchall_arrayref(undef, $max_rows); $self->_fetch_error($self->{query},$self->{sth},'db_get_rowblock',undef,@{$self->{params}}) if $self->{sth}->err(); } return $rows; } } sub db_finish { my $self = shift; my $transactional = shift; my $rollback = shift; my $log = shift; # since this is also called from DESTROY, no die() here! if (defined $self->{dbh} and defined $self->{sth}) { dbdebug($self,'db_finish',getlogger(__PACKAGE__)); $self->{sth}->finish(); $self->{sth} = undef; if ($transactional) { #$self->unlock_tables(); if ($rollback) { $self->db_rollback($log); } else { $self->db_commit(); } } $self->{query} = undef; $self->{params} = undef; } } sub ping { my $self = shift; #notimplementederror((ref $self) . ': ' . (caller(0))[3] . ' not implemented',getlogger(__PACKAGE__)); return 1; } 1;