7 use English qw(-no_match_vars);
9 use Rose::DBx::Cache::Anywhere;
11 use base qw(Rose::DB);
13 __PACKAGE__->db_cache_class('Rose::DBx::Cache::Anywhere');
14 __PACKAGE__->use_private_registry;
21 # runtime require to break circular include
22 require SL::DBConnect;
23 return SL::DBConnect->connect(@_);
27 my $domain = shift || SL::DB->default_domain;
28 my $type = shift || SL::DB->default_type;
30 ($domain, $type) = _register_db($domain, $type);
32 my $db = __PACKAGE__->new_or_cached(domain => $domain, type => $type);
38 create(undef, 'KIVITENDO');
42 create(undef, 'KIVITENDO_AUTH');
49 require SL::DBConnect;
50 my %specific_connect_settings;
51 my %common_connect_settings = (
53 european_dates => ((SL::DBConnect->get_datestyle || '') =~ m/european/i) ? 1 : 0,
59 if (($type eq 'KIVITENDO_AUTH') && $::auth && $::auth->{DB_config} && $::auth->session_tables_present) {
60 %specific_connect_settings = (
61 database => $::auth->{DB_config}->{db},
62 host => $::auth->{DB_config}->{host} || 'localhost',
63 port => $::auth->{DB_config}->{port} || 5432,
64 username => $::auth->{DB_config}->{user},
65 password => $::auth->{DB_config}->{password},
68 } elsif ($::auth && $::auth->client) {
69 my $client = $::auth->client;
70 %specific_connect_settings = (
71 database => $client->{dbname},
72 host => $client->{dbhost} || 'localhost',
73 port => $client->{dbport} || 5432,
74 username => $client->{dbuser},
75 password => $client->{dbpasswd},
78 } elsif (%::myconfig && $::myconfig{dbname}) {
79 %specific_connect_settings = (
80 database => $::myconfig{dbname},
81 host => $::myconfig{dbhost} || 'localhost',
82 port => $::myconfig{dbport} || 5432,
83 username => $::myconfig{dbuser},
84 password => $::myconfig{dbpasswd},
88 $type = 'KIVITENDO_EMPTY';
91 my %connect_settings = (%common_connect_settings, %specific_connect_settings);
92 my %flattened_settings = _flatten_settings(%connect_settings);
94 $domain = 'KIVITENDO' if $type =~ m/^KIVITENDO/;
95 $type .= join($SUBSCRIPT_SEPARATOR, map { ($_, $flattened_settings{$_} || '') } sort grep { $_ ne 'dbpasswd' } keys %flattened_settings);
96 my $idx = "${domain}::${type}";
98 if (!$_db_registered{$idx}) {
99 $_db_registered{$idx} = 1;
101 __PACKAGE__->register_db(domain => $domain,
107 return ($domain, $type);
110 sub _flatten_settings {
114 while (my ($key, $value) = each %settings) {
115 if ('HASH' eq ref $value) {
116 %flattened = ( %flattened, _flatten_settings(%{ $value }) );
118 $flattened{$key} = $value;
125 sub with_transaction {
126 my ($self, $code, @args) = @_;
128 return $code->(@args) if $self->in_transaction;
130 my (@result, $result);
137 ? $self->do_transaction(sub { @result = $code->(@args) })
138 : $self->do_transaction(sub { $result = $code->(@args) });
140 my $error = $self->error;
142 if ($error->isa('SL::X::DBError')) {
143 # gobble the exception
152 return wantarray ? @result : $result;
164 SL::DB - Database access class for all RDB objects
170 =item C<create $domain, $type>
172 Registers the database information with Rose, creates a cached
173 connection and executes initial SQL statements. Those can include
174 setting the time & date format to the user's preferences.
176 =item C<dbi_connect $dsn, $login, $password, $options>
178 Forwards the call to L<SL::DBConnect/connect> which connects to the
179 database. This indirection allows L<SL::DBConnect/connect> to route
180 the calls through L<DBIx::Log4Perl> if this is enabled in the
183 =item C<with_transaction $code_ref, @args>
185 Executes C<$code_ref> with parameters C<@args> within a transaction,
186 starting one only if none is currently active. Example:
188 return $self->db->with_transaction(sub {
189 # do stuff with $self
192 This is a wrapper around L<Rose::DB/do_transaction> that does a few additional
193 things, and should always be used in favour of the other:
197 =item Composition of transactions
199 When C<with_transaction> is called without a running transaction, a new one is
200 created. If it is called within a running transaction, it performs no
201 additional handling. This means that C<with_transaction> can be safely used
202 within another C<with_transaction>, whereas L<Rose::DB/do_transaction> can not.
206 C<with_transaction> adopts the behaviour of C<eval> in that it returns the
207 result of the inner block, and C<undef> if an error occured. This way you can
208 use the same pattern you would normally use with C<eval> for
211 SL::DB->client->with_transaction(sub {
213 # and return nominal true value
216 # transaction error handling
217 my $error = SL::DB->client->error;
220 or you can use it to safely calulate things.
224 The original L<Rose::DB/do_transaction> gobbles up all execptions and expects
225 the caller to manually check return value and error, and then to process all
226 exceptions as strings. This is very fragile and generally a step backwards from
227 proper exception handling.
229 C<with_transaction> only gobbles up exception that are used to signal an
230 error in the transaction, and returns undef on those. All other exceptions
231 bubble out of the transaction like normal, so that it is transparent to typoes,
232 runtime exceptions and other generally wanted things.
234 If you just use the snippet above, your code will catch everything related to
235 the transaction aborting, but will not catch other errors that might have been
236 thrown. The transaction will be rollbacked in both cases.
238 If you want to play nice in case your transaction is embedded in another
239 transaction, just rethrow the error:
241 $db->with_transaction(sub {
242 # code deep in the engine
244 }) or die $db->error;
256 Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>