File Coverage

blib/lib/Dancer/Plugin/MongoDB.pm
Criterion Covered Total %
statement 18 110 16.3
branch 0 62 0.0
condition 0 24 0.0
subroutine 6 13 46.1
pod n/a
total 24 209 11.4


line stmt bran cond sub pod time code
1             # ABSTRACT: MongoDB plugin for the Dancer micro framework
2             package Dancer::Plugin::MongoDB;
3              
4 1     1   470029 use strict;
  1         3  
  1         48  
5 1     1   6 use warnings;
  1         2  
  1         39  
6 1     1   1034 use Dancer::Plugin;
  1         93696  
  1         97  
7 1     1   11 use Mango;
  1         2  
  1         10  
8 1     1   27 use Scalar::Util 'blessed';
  1         1  
  1         50  
9 1     1   1119 use Dancer qw{:syntax};
  1         181106  
  1         6  
10              
11             my $dancer_version = (exists &dancer_version) ? int(dancer_version()) : 1;
12             my ($logger);
13             if ($dancer_version == 1) {
14             require Dancer::Config;
15             Dancer::Config->import();
16              
17             $logger = sub { Dancer::Logger->can($_[0])->($_[1]) };
18             } else {
19             $logger = sub { log @_ };
20             }
21              
22             =encoding utf8
23             =head1 NAME
24              
25             Dancer::Plugin::MongoDB - MongoDB connections as provided by Mango.
26              
27             =head1 STATUS
28              
29             Horribly under-tested, may induce seizures and sudden death. You have been warned.
30             Additionally, this module will require MongoDB 2.6+. This is primarily because Mango
31             requires it. You will get an error "MongoDB wire protocol version 2 required" if this
32             is not the case.
33              
34             =cut
35              
36             our $VERSION = 0.35;
37              
38             my $settings = undef;
39             my $conn = undef;
40             my $lasterror = undef;
41              
42             sub _load_db_settings {
43 0     0     $settings = plugin_setting;
44             }
45              
46             my %handles;
47             # Hashref used as key for default handle, so we don't have a magic value that
48             # the user could use for one of their connection names and cause problems
49             # (Kudos to Igor Bujna for the idea)
50             my $def_handle = {};
51              
52             ## return a connected MongoDB object
53             register mongo => sub {
54              
55 0     0     my ( $self, $arg ) = plugin_args(@_);
56              
57 0 0 0       $arg = shift if blessed($arg) and $arg->isa('Dancer::Core::DSL');
58              
59             # The key to use to store this handle in %handles. This will be either the
60             # name supplied to database(), the hashref supplied to database() (thus, as
61             # long as the same hashref of settings is passed, the same handle will be
62             # reused) or $def_handle if database() is called without args:
63              
64 0 0         _load_db_settings() if ( !$settings);
65              
66 0           my $handle_key;
67             my $conn_details; # connection settings to use.
68 0           my $handle;
69              
70              
71             # Accept a hashref of settings to use, if desired. If so, we use this
72             # hashref to look for the handle, too, so as long as the same hashref is
73             # passed to the database() keyword, we'll reuse the same handle:
74 0 0         if (ref $arg eq 'HASH') {
75 0           $handle_key = $arg;
76 0           $conn_details = $arg;
77             } else {
78 0 0         $handle_key = defined $arg ? $arg : $def_handle;
79 0           $conn_details = _get_settings($arg);
80 0 0         if (!$conn_details) {
81 0   0       $logger->(error => "No DB settings for " . ($arg || "default connection"));
82 0           return;
83             }
84             }
85              
86             # To be fork safe and thread safe, use a combination of the PID and TID (if
87             # running with use threads) to make sure no two processes/threads share
88             # handles. Implementation based on DBIx::Connector by David E. Wheeler.
89 0           my $pid_tid = $$;
90 0 0         $pid_tid .= '_' . threads->tid if $INC{'threads.pm'};
91              
92             # OK, see if we have a matching handle
93 0   0       $handle = $handles{$pid_tid}{$handle_key} || {};
94              
95 0 0         if ($handle->{dbh}) {
96             # If we should never check, go no further:
97 0 0         if (!$conn_details->{connection_check_threshold}) {
98 0           return $handle->{dbh};
99             }
100              
101 0 0 0       if ($handle->{dbh}{Active} && $conn_details->{connection_check_threshold} &&
      0        
102             time - $handle->{last_connection_check}
103             < $conn_details->{connection_check_threshold})
104             {
105 0           return $handle->{dbh};
106             } else {
107 0 0         if (_check_connection($handle->{dbh})) {
108 0           $handle->{last_connection_check} = time;
109 0           return $handle->{dbh};
110             } else {
111              
112 0           $logger->(debug => "Database connection went away, reconnecting");
113 0           execute_hook('database_connection_lost', $handle->{dbh});
114              
115 0           return $handle->{dbh}= _get_connection($conn_details);
116              
117             }
118             }
119             } else {
120             # Get a new connection
121 0 0         if ($handle->{dbh} = _get_connection($conn_details)) {
122 0           $handle->{last_connection_check} = time;
123 0           $handles{$pid_tid}{$handle_key} = $handle;
124              
125 0 0 0       if (ref $handle_key && ref $handle_key ne ref $def_handle) {
126             # We were given a hashref of connection settings. Shove a
127             # reference to that hashref into the handle, so that the hashref
128             # doesn't go out of scope for the life of the handle.
129             # Otherwise, that area of memory could be re-used, and, given
130             # different DB settings in a hashref that just happens to have
131             # the same address, we'll happily hand back the original handle.
132             # See http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=665221
133             # Thanks to Sam Kington for suggesting this fix :)
134 0           $handle->{_orig_settings_hashref} = $handle_key;
135             }
136 0           return $handle->{dbh};
137             } else {
138 0           return;
139             }
140             }
141             };
142              
143             register_hook(qw(mongodb_connected
144             mongodb_connection_lost
145             mongodb_connection_failed
146             mongodb_error));
147             register_plugin(for_versions => ['1', '2']);
148              
149             # Given the settings to use, try to get a database connection
150             sub _get_connection {
151 0     0     my $settings = shift;
152              
153             # Assemble the Connection String:
154 0 0 0       my $dsn = 'mongodb://' .
155             ( $settings->{host} || 'localhost' ) .
156             ( defined $settings->{port} ? ':' . $settings->{port} : () );
157              
158 0           my $dbh = Mango->new($dsn);
159              
160 0 0         $dbh->default_db($settings->{db_name})
161             if defined $settings->{db_name};
162              
163 0 0 0       if (defined $settings->{username} && defined $settings->{password}) {
164 0           push @{$settings->{db_credentials}}, [ $settings->{db_name}, $settings->{username}, $settings->{password}];
  0            
165             }
166              
167              
168 0 0 0       if (defined $settings->{db_credentials} and ref $settings->{db_credentials} eq 'ARRAY') {
169 0           $dbh->credentials($settings->{db_credentials});
170             }
171              
172 0 0         if (defined $settings->{ioloop}) {
173 0           my ( $module, $function ) = split(/\-\>/, $settings->{ioloop});
174 0           $dbh->ioloop($module->$function);
175             }
176              
177 0 0         if (defined $settings->{j}) {
178 0           $dbh->j($settings->{j})
179             };
180              
181 0 0         if (defined $settings->{max_bson_size}) {
182 0           $dbh->max_bson_size($settings->{max_bson_size})
183             };
184              
185 0 0         if (defined $settings->{max_connections}) {
186 0           $dbh->max_connections($settings->{max_connections})
187             }
188              
189 0 0         if (defined $settings->{max_write_batch_size}) {
190 0           $dbh->max_write_batch_size($settings->{max_write_batch_size})
191             }
192              
193 0 0         if ( defined $settings->{protocol}) {
194 0           my ( $module, $function ) = split(/\-\>/, $settings->{protocol});
195 0           $dbh->protocol($module->$function);
196             }
197              
198 0 0         if ( defined $settings->{w}) {
199 0           $dbh->w($settings->{w})
200             }
201              
202 0 0         if ( defined $settings->{wtimeout}) {
203 0           $dbh->wtimeout($settings->{wtimeout})
204             }
205              
206             #$dbh->on( error => \&_mango_error() );
207             #$dbh->on( connection => \&_mango_connection() );
208              
209 0 0         if (!$dbh) {
210 0           $logger->(error => "Database connection failed - " . $lasterror);
211 0           execute_hook('database_connection_failed', $settings);
212 0           return;
213             }
214              
215 0           execute_hook('database_connected', $dbh);
216              
217 0           return $dbh;
218             }
219              
220             # Check the connection is alive
221             sub _check_connection {
222 0     0     my $dbh = shift;
223 0 0         return unless $dbh;
224              
225 0           my $curs;
226              
227 0           $lasterror = undef;
228              
229 0           eval {
230 0           $curs = $dbh->db($settings->{db_name})->collection('prototype')->find_one();
231             };
232              
233 0 0         if (!defined $lasterror) {
234 0           return 1;
235             }
236              
237 0           return;
238             }
239              
240             sub _mango_error {
241 0     0     my ( $mango, $err ) = @_;
242 0           $lasterror = $err;
243 0           return;
244             }
245              
246             sub _mango_connection {
247 0     0     return;
248             }
249              
250             sub _get_settings {
251 0     0     my $name = shift;
252 0           my $return_settings;
253              
254             # If no name given, just return the default settings
255 0 0         if (!defined $name) {
256 0           $return_settings = { %$settings };
257             # Yeah, you can have ZERO settings in Mongo.
258             } else {
259             # If there are no named connections in the config, bail now:
260 0 0         return unless exists $settings->{connections};
261              
262             # OK, find a matching config for this name:
263 0 0         if (my $named_settings = $settings->{connections}{$name}) {
264             # Take a (shallow) copy of the settings, so we don't change them
265 0           $return_settings = { %$named_settings };
266             } else {
267             # OK, didn't match anything
268 0           $logger->('error',
269             "Asked for a database handle named '$name' but no matching "
270             ."connection details found in config"
271             );
272             }
273             }
274              
275             # If the setting wasn't provided, default to 30 seconds; if a false value is
276             # provided, though, leave it alone. (Older versions just checked for
277             # truthiness, so a value of zero would still default to 30 seconds, which
278             # isn't ideal.)
279 0 0         if (!exists $return_settings->{connection_check_threshold}) {
280 0           $return_settings->{connection_check_threshold} = 30;
281             }
282              
283 0           return $return_settings;
284              
285             }
286             1;
287              
288             __END__