Projekt

Allgemein

Profil

Herunterladen (6,72 KB) Statistiken
| Zweig: | Markierung: | Revision:
82515b2d Sven Schöling
package SL::DB;

use strict;

use Carp;
use Data::Dumper;
use English qw(-no_match_vars);
use Rose::DB;
37d7b869 Sven Schöling
use Rose::DBx::Cache::Anywhere;
abc355d2 Moritz Bunkus
use Scalar::Util qw(blessed);
82515b2d Sven Schöling
use base qw(Rose::DB);

37d7b869 Sven Schöling
__PACKAGE__->db_cache_class('Rose::DBx::Cache::Anywhere');
82515b2d Sven Schöling
__PACKAGE__->use_private_registry;

576c2a14 Moritz Bunkus
my (%_db_registered);
82515b2d Sven Schöling
54d656bd Moritz Bunkus
sub dbi_connect {
shift;

6f741b43 Sven Schöling
# runtime require to break circular include
require SL::DBConnect;
22c02125 Moritz Bunkus
return SL::DBConnect->connect(@_);
54d656bd Moritz Bunkus
}

82515b2d Sven Schöling
sub create {
my $domain = shift || SL::DB->default_domain;
my $type = shift || SL::DB->default_type;

45aeeb13 Moritz Bunkus
($domain, $type) = _register_db($domain, $type);
82515b2d Sven Schöling
my $db = __PACKAGE__->new_or_cached(domain => $domain, type => $type);

return $db;
}

5a08d9e4 Sven Schöling
sub client {
create(undef, 'KIVITENDO');
}

sub auth {
create(undef, 'KIVITENDO_AUTH');
}

82515b2d Sven Schöling
sub _register_db {
my $domain = shift;
my $type = shift;

6f741b43 Sven Schöling
require SL::DBConnect;
f9c88c3f Moritz Bunkus
my %specific_connect_settings;
my %common_connect_settings = (
driver => 'Pg',
576c2a14 Moritz Bunkus
european_dates => ((SL::DBConnect->get_datestyle || '') =~ m/european/i) ? 1 : 0,
f9c88c3f Moritz Bunkus
connect_options => {
dbda14c2 Moritz Bunkus
pg_enable_utf8 => 1,
f9c88c3f Moritz Bunkus
},
);

if (($type eq 'KIVITENDO_AUTH') && $::auth && $::auth->{DB_config} && $::auth->session_tables_present) {
%specific_connect_settings = (
database => $::auth->{DB_config}->{db},
host => $::auth->{DB_config}->{host} || 'localhost',
port => $::auth->{DB_config}->{port} || 5432,
username => $::auth->{DB_config}->{user},
password => $::auth->{DB_config}->{password},
);

} elsif ($::auth && $::auth->client) {
my $client = $::auth->client;
%specific_connect_settings = (
database => $client->{dbname},
host => $client->{dbhost} || 'localhost',
port => $client->{dbport} || 5432,
username => $client->{dbuser},
password => $client->{dbpasswd},
);

} elsif (%::myconfig && $::myconfig{dbname}) {
%specific_connect_settings = (
database => $::myconfig{dbname},
host => $::myconfig{dbhost} || 'localhost',
port => $::myconfig{dbport} || 5432,
username => $::myconfig{dbuser},
password => $::myconfig{dbpasswd},
);

} else {
d8ac0828 Moritz Bunkus
$type = 'KIVITENDO_EMPTY';
}

f9c88c3f Moritz Bunkus
my %connect_settings = (%common_connect_settings, %specific_connect_settings);
18a9b190 Moritz Bunkus
my %flattened_settings = _flatten_settings(%connect_settings);

f9c88c3f Moritz Bunkus
$domain = 'KIVITENDO' if $type =~ m/^KIVITENDO/;
$type .= join($SUBSCRIPT_SEPARATOR, map { ($_, $flattened_settings{$_} || '') } sort grep { $_ ne 'dbpasswd' } keys %flattened_settings);
my $idx = "${domain}::${type}";
a97d97a0 Moritz Bunkus
if (!$_db_registered{$idx}) {
$_db_registered{$idx} = 1;

__PACKAGE__->register_db(domain => $domain,
type => $type,
%connect_settings,
);
}

return ($domain, $type);
82515b2d Sven Schöling
}

18a9b190 Moritz Bunkus
sub _flatten_settings {
my %settings = @_;
my %flattened = ();

while (my ($key, $value) = each %settings) {
if ('HASH' eq ref $value) {
%flattened = ( %flattened, _flatten_settings(%{ $value }) );
} else {
$flattened{$key} = $value;
}
}

return %flattened;
}

7cfa1f2a Moritz Bunkus
sub with_transaction {
my ($self, $code, @args) = @_;

0ade6272 Moritz Bunkus
return $code->(@args) if $self->in_transaction;

660c7e53 Sven Schöling
my (@result, $result);
my $rv = 1;

local $@;

eval {
wantarray
? $self->do_transaction(sub { @result = $code->(@args) })
: $self->do_transaction(sub { $result = $code->(@args) });
} or do {
my $error = $self->error;
abc355d2 Moritz Bunkus
if (blessed $error) {
660c7e53 Sven Schöling
if ($error->isa('SL::X::DBError')) {
# gobble the exception
} else {
$error->rethrow;
}
} else {
die $self->error;
}
};

return wantarray ? @result : $result;
7cfa1f2a Moritz Bunkus
}

82515b2d Sven Schöling
1;
7cfa1f2a Moritz Bunkus
__END__

=pod

=encoding utf8

=head1 NAME

SL::DB - Database access class for all RDB objects

=head1 FUNCTIONS

=over 4

=item C<create $domain, $type>

Registers the database information with Rose, creates a cached
connection and executes initial SQL statements. Those can include
setting the time & date format to the user's preferences.

=item C<dbi_connect $dsn, $login, $password, $options>

Forwards the call to L<SL::DBConnect/connect> which connects to the
database. This indirection allows L<SL::DBConnect/connect> to route
the calls through L<DBIx::Log4Perl> if this is enabled in the
configuration.

=item C<with_transaction $code_ref, @args>

0ade6272 Moritz Bunkus
Executes C<$code_ref> with parameters C<@args> within a transaction,
2d43271a Moritz Bunkus
starting one only if none is currently active. Example:
7cfa1f2a Moritz Bunkus
return $self->db->with_transaction(sub {
# do stuff with $self
});

660c7e53 Sven Schöling
This is a wrapper around L<Rose::DB/do_transaction> that does a few additional
things, and should always be used in favour of the other:
0ade6272 Moritz Bunkus
660c7e53 Sven Schöling
=over 4
2d43271a Moritz Bunkus
660c7e53 Sven Schöling
=item Composition of transactions
2d43271a Moritz Bunkus
660c7e53 Sven Schöling
When C<with_transaction> is called without a running transaction, a new one is
created. If it is called within a running transaction, it performs no
additional handling. This means that C<with_transaction> can be safely used
within another C<with_transaction>, whereas L<Rose::DB/do_transaction> can not.
2d43271a Moritz Bunkus
660c7e53 Sven Schöling
=item Return values
2d43271a Moritz Bunkus
660c7e53 Sven Schöling
C<with_transaction> adopts the behaviour of C<eval> in that it returns the
result of the inner block, and C<undef> if an error occured. This way you can
use the same pattern you would normally use with C<eval> for
C<with_transaction>:
2d43271a Moritz Bunkus
660c7e53 Sven Schöling
SL::DB->client->with_transaction(sub {
# do stuff
# and return nominal true value
1;
}) or do {
# transaction error handling
my $error = SL::DB->client->error;
}
2d43271a Moritz Bunkus
660c7e53 Sven Schöling
or you can use it to safely calulate things.
0ade6272 Moritz Bunkus
660c7e53 Sven Schöling
=item Error handling
2d43271a Moritz Bunkus
660c7e53 Sven Schöling
The original L<Rose::DB/do_transaction> gobbles up all execptions and expects
the caller to manually check return value and error, and then to process all
exceptions as strings. This is very fragile and generally a step backwards from
proper exception handling.

C<with_transaction> only gobbles up exception that are used to signal an
error in the transaction, and returns undef on those. All other exceptions
bubble out of the transaction like normal, so that it is transparent to typoes,
runtime exceptions and other generally wanted things.

If you just use the snippet above, your code will catch everything related to
the transaction aborting, but will not catch other errors that might have been
thrown. The transaction will be rollbacked in both cases.
2d43271a Moritz Bunkus
660c7e53 Sven Schöling
If you want to play nice in case your transaction is embedded in another
transaction, just rethrow the error:

$db->with_transaction(sub {
# code deep in the engine
1;
}) or die $db->error;

=back
0ade6272 Moritz Bunkus
7cfa1f2a Moritz Bunkus
=back

=head1 BUGS

Nothing here yet.

=head1 AUTHOR

Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>

=cut