kivitendo/SL/DB.pm @ fbc8b548
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
|
||||
} else {
|
||||
$error->rethrow;
|
||||
}
|
||||
} 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
|
||
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 | |||
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
|
||
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
|
||||
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
|