Projekt

Allgemein

Profil

Herunterladen (6,82 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;
b9d5cb7b Moritz Bunkus
use SL::DB::Helper::Cache;
abc355d2 Moritz Bunkus
use Scalar::Util qw(blessed);
82515b2d Sven Schöling
use base qw(Rose::DB);

b9d5cb7b Moritz Bunkus
__PACKAGE__->db_cache_class('SL::DB::Helper::Cache');
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/;
94ceb0fc Geoffrey Richardson
$type .= join($SUBSCRIPT_SEPARATOR, map { ($_, $flattened_settings{$_} || '') } sort grep { $_ ne 'password' } keys %flattened_settings);
f9c88c3f Moritz Bunkus
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 $@;
611b4916 Martin Helmling
my $return_array = wantarray;
660c7e53 Sven Schöling
eval {
611b4916 Martin Helmling
$return_array
660c7e53 Sven Schöling
? $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
5b43c81a Jan Büren
} elsif ($error->can('rethrow')) {
660c7e53 Sven Schöling
$error->rethrow;
5b43c81a Jan Büren
} else {
croak $self->error;
660c7e53 Sven Schöling
}
} else {
die $self->error;
}
};

611b4916 Martin Helmling
return $return_array ? @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
37f2ab4d Geoffrey Richardson
result of the inner block, and C<undef> if an error occurred. This way you can
660c7e53 Sven Schöling
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
fbc8b548 Geoffrey Richardson
The original L<Rose::DB/do_transaction> gobbles up all exceptions and expects
the caller to manually check the 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.
660c7e53 Sven Schöling
fbc8b548 Geoffrey Richardson
C<with_transaction> only gobbles up exceptions that are used to signal an
660c7e53 Sven Schöling
error in the transaction, and returns undef on those. All other exceptions
3bd31e0d Geoffrey Richardson
bubble out of the transaction like normal, so that it is transparent to typos,
660c7e53 Sven Schöling
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
fbc8b548 Geoffrey Richardson
thrown. The transaction will be rolled back 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