Nur noch ein DB-Hanlde pro Request zum Client aufmachen
[kivitendo-erp.git] / SL / DB.pm
1 package SL::DB;
2
3 use strict;
4
5 use Carp;
6 use Data::Dumper;
7 use English qw(-no_match_vars);
8 use Rose::DB;
9 use Rose::DBx::Cache::Anywhere;
10
11 use base qw(Rose::DB);
12
13 __PACKAGE__->db_cache_class('Rose::DBx::Cache::Anywhere');
14 __PACKAGE__->use_private_registry;
15
16 my (%_db_registered);
17
18 sub dbi_connect {
19   shift;
20
21   # runtime require to break circular include
22   require SL::DBConnect;
23   return SL::DBConnect->connect(@_);
24 }
25
26 sub create {
27   my $domain = shift || SL::DB->default_domain;
28   my $type   = shift || SL::DB->default_type;
29
30   ($domain, $type) = _register_db($domain, $type);
31
32   my $db = __PACKAGE__->new_or_cached(domain => $domain, type => $type);
33
34   return $db;
35 }
36
37 sub client {
38   create(undef, 'KIVITENDO');
39 }
40
41 sub auth {
42   create(undef, 'KIVITENDO_AUTH');
43 }
44
45 sub _register_db {
46   my $domain = shift;
47   my $type   = shift;
48
49   require SL::DBConnect;
50   my %specific_connect_settings;
51   my %common_connect_settings = (
52     driver           => 'Pg',
53     european_dates   => ((SL::DBConnect->get_datestyle || '') =~ m/european/i) ? 1 : 0,
54     connect_options  => {
55       pg_enable_utf8 => 1,
56     },
57   );
58
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},
66     );
67
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},
76     );
77
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},
85     );
86
87   } else {
88     $type = 'KIVITENDO_EMPTY';
89   }
90
91   my %connect_settings   = (%common_connect_settings, %specific_connect_settings);
92   my %flattened_settings = _flatten_settings(%connect_settings);
93
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}";
97
98   if (!$_db_registered{$idx}) {
99     $_db_registered{$idx} = 1;
100
101     __PACKAGE__->register_db(domain => $domain,
102                              type   => $type,
103                              %connect_settings,
104                             );
105   }
106
107   return ($domain, $type);
108 }
109
110 sub _flatten_settings {
111   my %settings  = @_;
112   my %flattened = ();
113
114   while (my ($key, $value) = each %settings) {
115     if ('HASH' eq ref $value) {
116       %flattened = ( %flattened, _flatten_settings(%{ $value }) );
117     } else {
118       $flattened{$key} = $value;
119     }
120   }
121
122   return %flattened;
123 }
124
125 sub with_transaction {
126   my ($self, $code, @args) = @_;
127
128   return $code->(@args) if $self->in_transaction;
129   if (wantarray) {
130     my @result;
131     return $self->do_transaction(sub { @result = $code->(@args) }) ? @result : ();
132
133   } else {
134     my $result;
135     return $self->do_transaction(sub { $result = $code->(@args) }) ? $result : undef;
136   }
137 }
138
139 1;
140 __END__
141
142 =pod
143
144 =encoding utf8
145
146 =head1 NAME
147
148 SL::DB - Database access class for all RDB objects
149
150 =head1 FUNCTIONS
151
152 =over 4
153
154 =item C<create $domain, $type>
155
156 Registers the database information with Rose, creates a cached
157 connection and executes initial SQL statements. Those can include
158 setting the time & date format to the user's preferences.
159
160 =item C<dbi_connect $dsn, $login, $password, $options>
161
162 Forwards the call to L<SL::DBConnect/connect> which connects to the
163 database. This indirection allows L<SL::DBConnect/connect> to route
164 the calls through L<DBIx::Log4Perl> if this is enabled in the
165 configuration.
166
167 =item C<with_transaction $code_ref, @args>
168
169 Executes C<$code_ref> with parameters C<@args> within a transaction,
170 starting one only if none is currently active. Example:
171
172   return $self->db->with_transaction(sub {
173     # do stuff with $self
174   });
175
176 There are two big differences between C<with_transaction> and
177 L<Rose::DB/do_transaction>: the handling of an already-running
178 transaction and the handling of return values.
179
180 The first difference revolves around when a transaction is started and
181 committed/rolled back. Rose's C<do_transaction> will always start one,
182 then execute the code reference and commit afterwards (or roll back if
183 an exception occurs).
184
185 This prevents the caller from combining several pieces of code using
186 C<do_transaction> reliably as results committed by an inner
187 transaction will be permanent even if the outer transaction is rolled
188 back.
189
190 Therefore our C<with_transaction> works differently: it will only
191 start a transaction if no transaction is currently active on the
192 database connection.
193
194 The second big difference to L<Rose::DB/do_transaction> is the
195 handling of returned values. Basically our C<with_transaction> will
196 return the values that the code reference C<$code_ref> returns (or
197 C<undef> if the transaction was rolled back). Rose's C<do_transaction>
198 on the other hand will only return a value signaling the transaction's
199 status.
200
201 In more detail:
202
203 =over 2
204
205 =item * If a transaction is already active then C<with_transaction>
206 will simply return the result of calling C<$code_ref> as-is preserving
207 context.
208
209 =item * If no transaction is started then C<$code_ref> will be wrapped
210 in one. C<with_transaction>'s return value depends on the result of
211 that transaction. If the it succeeds then the return value of
212 C<$code_ref> will be returned preserving context. Otherwise C<undef>
213 will be returned in scalar context and an empty list in list context.
214
215 =back
216
217 So if you want to differentiate between "transaction failed" and
218 "succeeded" then your C<$code_ref> should never return C<undef>
219 itself.
220
221 =back
222
223 =head1 BUGS
224
225 Nothing here yet.
226
227 =head1 AUTHOR
228
229 Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>
230
231 =cut