DBIx-QueryByName

 view release on metacpan or  search on metacpan

lib/DBIx/QueryByName/SthPool.pm  view on Meta::CPAN

            $self->{sths}->{$$}->{$query}->finish;
        }
    }
    delete $self->{sths}->{$$};
}

sub _prepare {
    my ($self,$query) = @_;
    my $log = get_logger();

    my ($session,$sql) = $self->{querypool}->get_query($query);

    if (defined $self->_get_sth($query)) {
        debug "Query is already prepared. Using cached value.";
        return $self->_get_sth($query);
    }

    my $dbh = $self->{dbhpool}->connect($session);

    debug "Preparing query $query";
    my $sth = $dbh->prepare($sql);

    # TODO: add more verbose error description?
    # TODO: retry in some smart way?
    if (!defined $sth) {
        $log->logcroak("failed to prepare query [$query]. Trace: ".cluck);
    }

    $self->_add_sth($query,$sth);
    return $sth;
}

sub prepare_and_execute {
    my ($self,%args) = @_;
    my $log = get_logger();

    my $query           = $args{query_name} || $log->logcroak("undefined query name");
    my $bulk_insertion  = $args{bulk_insertion};
    $log->logcroak("undefined bulk insertion flag") if (!defined $bulk_insertion);

    $log->logcroak("undefined or wrong query args")
        if (!exists $args{query_args} || ref $args{query_args} ne 'ARRAY');
    my @args = @{$args{query_args}};

    my ($session,undef) = $self->{querypool}->get_query($query);

    my $retry = $self->{querypool}->get_retry_attribute($query);
    my $error_connection_lost   = qr/could not connect to server|no connection to the server|terminating connection due to administrator command/;
    my $error_state_unknown = qr/server closed the connection unexpectedly|could not receive data from server|terminating connection due to administrator command/;


    my $sth = $self->_prepare($query);

    my $rv;
    my $error_reported = 0;
    while (1) {

        # Normally, if traffic between the client and the database
        # server is interupted (cable cut, whatever), the client will
        # timeout after 1min (observed on osx). But it has been
        # observed on some setups (client on linux, server blocked by
        # drop rule in firewall) that the client hang forever in
        # execute(). The following code is a workaround:
        #
        #         my $did_timeout = 0;
        #         eval {
        #             local $SIG{ALRM} = sub { $did_timeout = 1; die 'TIMEOUT' };
        #             alarm($self->{execute_timeout});
        #             # call execute
        #             alarm(0);
        #         };
        #         alarm(0);
        #
        #         if ($did_timeout) {
        #         } elsif ($@) {}
        #
        # But this code works only if perl is running with 'unsafe'
        # signal handling (env PERL_SIGNALS=unsafe). And we don't want
        # to compromise DBD::Pg by interupting impromptuously.

        # WARNING: execute* might hang forever
        if ($bulk_insertion == 1) {
            unless (scalar @args && ref $args[0] eq 'ARRAY') {
                $log->logcroak("invalid data structure of args for bulk insertion:" . Dumper(\@args));
            }
            debug "Calling execute_array for query $query";
            $rv = $sth->execute_array({},@args);
        } elsif($bulk_insertion == 0) {
            debug "Calling execute for query $query with args" . Dumper(\@args);
            $rv = $sth->execute(@args);
        } else {
            $log->logcroak("unexpected value of bulk_insertion: " . Dumper($bulk_insertion));
        }

        if (!defined $rv) {
            my $err = $sth->err || 99999999999999;
            my $errstr = $sth->errstr || '';

            debug "An error occured while executing query [$query] [$err] [$errstr]";

            # if connection error while executing, retry
            # TODO: support error messages per database type
            # NOTE: if execute times-out properly, it raises an error with code 7 and text 'could not receive data from server: Operation timed out'
            if (
                $err == 7 &&
                (
                    ($retry eq 'safe' && $errstr =~ m/$error_connection_lost/)
                    ||
                    ($retry eq 'always' && ($errstr =~ m/$error_connection_lost/ || $errstr =~ m/$error_state_unknown/))
                )
            ) {

                $log->error("Query $query failed, will try again, Error code [$err], Error message [$errstr]. Trace: ".cluck)
                    if ($error_reported == 0);

                # try to reconnect to database
                my $dbh = $self->{dbhpool}->connect($session);
                unless ($dbh->ping()) {
                    debug "Can ping database. Trying to disconnect, re-connect and re-prepare.";
                    $self->{dbhpool}->disconnect($session);
                    $self->finish_all_sths();  # TODO: do we really want to finish ALL queries or only those in this session?



( run in 1.043 second using v1.01-cache-2.11-cpan-39bf76dae61 )