File Coverage

blib/lib/Rose/Planter/DB.pm
Criterion Covered Total %
statement 59 105 56.1
branch 10 52 19.2
condition 9 26 34.6
subroutine 12 16 75.0
pod 7 8 87.5
total 97 207 46.8


line stmt bran cond sub pod time code
1             =head1 NAME
2              
3             Rose::Planter::DB -- base db class for Rose-Planter planted objects.
4              
5             =head1 DESCRIPTION
6              
7             This is derived from Rose::DB, but adds a few class methods as
8             described below. Also, it allows database to be registered
9             based on configuration files.
10              
11             =head1 METHODS
12              
13             =cut
14              
15             package Rose::Planter::DB;
16 2     2   18718 use Log::Log4perl qw /:easy/;
  2         55886  
  2         14  
17              
18 2     2   3163 eval { use Module::Build::Database; };
  2         289906  
  2         67  
19 2     2   1715 use DateTime::Format::Pg;
  2         200519  
  2         19  
20 2     2   1937 use DBIx::Connector;
  2         28058  
  2         69  
21 2     2   15 use List::MoreUtils qw/mesh/;
  2         3  
  2         30  
22              
23 2     2   876 use base 'Rose::DB';
  2         3  
  2         1576  
24              
25 2     2   80033 use strict;
  2         5  
  2         39  
26 2     2   11 use warnings;
  2         4  
  2         2255  
27              
28             our %Registered; # hash from app names to db class names.
29              
30             =over
31              
32             =cut
33              
34             =item DateTime::Duration::TO_JSON
35              
36             This is defined here to serialize durations as postgres intervals.
37              
38             =cut
39              
40             sub DateTime::Duration::TO_JSON {
41 0     0 0 0 my $d = shift;
42 0         0 return DateTime::Format::Pg->format_duration($d);
43             }
44              
45             =item dbi_connect
46              
47             Connect and retain the db handle. Also, set the time zone to UTC.
48              
49             =cut
50              
51             {
52             my %connections;
53             sub dbi_connect {
54 3     3 1 22999 my $self = shift;
55 3   33     13 my $class = ref $self || $self;
56             # This causes a "set time zone" command, so we only get utc times.
57             # see http://www.postgresql.org/docs/9.0/static/datatype-datetime.html#DATATYPE-TIMEZONES
58 3         22 $ENV{PGTZ} = "UTC";
59 3   66     35 $connections{$class} ||= DBIx::Connector->new(@_);
60             # See http://archives.postgresql.org/pgsql-performance/2011-02/msg00493.php
61 3         53 $connections{$class}->dbh->{pg_server_prepare} = 0;
62 3         13639 return $connections{$class}->dbh;
63             }
64             }
65              
66             =item release_dbh
67              
68             Overridden to hold onto dbh's.
69              
70             =cut
71              
72             sub release_dbh {
73             # probably there's a better way to do this, but I couldn't stop the handles
74             # from being released :(
75 25     25 1 109765 return 0;
76             }
77              
78             =item register_databases
79              
80             Register all the rose databases for this class.
81              
82             Arguments :
83              
84             module_name: The name of the perl module for which we are registering
85             databases. This will be used to check for an environment variable
86             named (uc $module_name)."_LIVE" to see if the live database configuration
87             should be used. Also, if a unit test suite is running, the current
88             Module::Build object will indicate that this module is being tested
89             and hence a test database should be used.
90              
91             register_params: A hash of parameters to be sent verbatim to Rose::DB::register_db.
92              
93             conf: a configuration object which will be queried as follows :
94              
95             $conf->db : parameters for the database.
96              
97             This should return a hash with keys such as "database", "schema", and "host"
98             which correspond to the parameters sent to Rose::DB::register_db.
99              
100             If L is being used, the "test" database will be
101             determined using information stored in the _build directory. This allows
102             the same database to be re-used during an entire './Build test'.
103              
104             When HARNESS_ACTIVE is true, conf should not be passed.
105              
106             =cut
107              
108             sub register_databases {
109 1     1 1 737 my $class = shift;
110 1         4 my %args = @_;
111 1 50       5 my $module_name = $args{module_name} or die "no module name passed";
112 1         3 my $conf = $args{conf};
113 1   50     3 my $register_params = $args{register_params} || {};
114             my $mbd = $ENV{HARNESS_ACTIVE}
115 1 50 33     52 && Module::Build::Database->can('current')
116             && -d './_build'
117             ? Module::Build::Database->current : undef;
118 1   33     67276 my $we_are_testing = ( $mbd && $mbd->module_name eq $module_name );
119 1         19 my $live_env_var = ( uc $module_name ) . '_LIVE';
120 1 50       12 my $we_are_live = $ENV{$live_env_var} ? 1 : 0;
121 1 50 33     12 die "no conf argument passed" if !$conf && !$we_are_testing;
122              
123 1   33     56 $Registered{$module_name} = (ref $class || $class);
124 1         30 my %default = (
125             type => "main",
126             driver => "Pg",
127             connect_options => {
128             PrintError => 1,
129             RaiseError => 0,
130             },
131             %$register_params,
132             );
133              
134 1         37 $class->default_type("main");
135              
136 1 50       19 if ($we_are_testing) {
137             # If register_params was sent, this may be a configuration for the test.
138             # (Once we have a Module::Build::Database::SQlite, this may not be necessary)
139 1 50 33     17 die "ERROR: no test db instance found. Please run ./Build dbtest --leave-running=1\n\n "
140             unless $mbd->notes("dbtest_host") || $register_params;
141 1 50       36 my %opts = %{ $mbd->can('database_options') ? $mbd->database_options : {} };
  1         17  
142 1 50       7 if ($opts{name}) {
143 0         0 $opts{database} = delete $opts{name};
144             };
145 1 50       4 $opts{host} = $mbd->notes("dbtest_host") if $mbd->notes("dbtest_host");
146             # sanitize these, since MBD sanitizes when it starts a database
147 1         24 delete $ENV{PGPORT};
148 1         7 delete $ENV{PGUSER};
149 1         19 $class->register_db( %default, %opts, domain => "test" );
150 1         9959 $class->default_domain("test");
151 1         366 return;
152             }
153              
154             # Just use "db" for the settings.
155 0 0       0 if ($conf->db(default => '')) {
156 0 0       0 my $domain = $we_are_live ? "live" : "dev";
157 0         0 eval {
158 0         0 $class->register_db( %default, domain => $domain, $conf->db );
159             };
160 0 0       0 warn "Error registering database : $@" if $@;
161 0         0 $class->default_domain($domain);
162 0         0 return;
163             }
164              
165 0         0 warn "'db' may now be used instead of 'databases->dev' in the configuration file.";
166             # Old way, for backwards compatibility.
167 0 0       0 unless ($conf->databases(is_defined => 1)) {
168 0         0 warn "No databases defined in configuration file.";
169 0         0 $conf->databases(default => {});
170             }
171              
172 0 0       0 warn "No dev database was defined in the configuration file.\n" unless $conf->databases->dev(is_defined => 1);
173 0         0 $conf->databases->dev(default => {});
174              
175 0 0       0 $class->register_db( %default, domain => "dev", $conf->databases->dev ) if $conf->databases->dev(is_defined => 1);
176 0 0       0 $class->register_db( %default, domain => "live", $conf->databases->live ) if $conf->databases->live(is_defined => 1);
177              
178 0 0       0 $class->default_domain( $we_are_live ? "live" : "dev" );
179             }
180              
181             =item registered_by
182              
183             Given a module name, return the name of the Rose::DB-derived
184             class which called register_databases.
185              
186             =cut
187              
188             sub registered_by {
189 0     0 1 0 my $class = shift;
190 0 0       0 my $module_name = shift or die "missing required parameter module_name";
191 0         0 return $Registered{$module_name};
192             }
193              
194             =item load_golden
195              
196             Load a golden dataset into the database.
197              
198             =cut
199              
200             sub load_golden {
201 0     0 1 0 my $class = shift;
202              
203 0 0       0 LOGDIE "Will not load golden dataset unless the database domain is test or dev"
204             unless $class->domain =~ /^(dev|test)$/;
205              
206 0         0 INFO "Loading golden dataset, domain : ".$class->domain;
207 0         0 LOGDIE "not yet implemented";
208             }
209              
210             =item has_primary_key [ TABLE | PARAMS ]
211              
212             Just like the overridden method in Rose::DB.pm except that
213             it ignores database objects that begin with 'v_'. This
214             provides a naming convention to avoid warnings for missing
215             keys when loading views.
216              
217             =cut
218              
219             sub has_primary_key {
220 2     2 1 1237 my $self = shift;
221 2         4 my $table = shift;
222 2 50       9 return 1 if $table =~ /^v_/;
223 2         21 $self->SUPER::has_primary_key($table);
224             }
225              
226             =item do_sql
227              
228             Do some sql and return the result as an arrayref of hashrefs.
229              
230             =cut
231              
232             sub do_sql {
233 0     0 1   my $class = shift;
234 0           my $sql = shift;
235 0           my @bind = @_;
236 0 0         my $obj = (ref $class ? $class : $class->new_or_cached);
237 0           my $sth = $obj->dbh->prepare($sql);
238 0 0         $sth->execute(@bind) or die $sth->errstr;
239 0           my $types = $sth->{'pg_type'};
240 0           my $names = $sth->{'NAME'};
241 0           my $res = $sth->fetchall_arrayref({});
242 0 0 0       return $res unless ref $names && ref $types;
243              
244             # Force all bigints into numeric context for JSON. (see JSON::XS)
245 0           my %name2type = mesh @$names, @$types;
246 0 0         return $res unless grep /int8/, @$types;
247 0           my @nums;
248 0           for (@$names) {
249 0 0         push @nums, $_ if $name2type{$_} eq 'int8';
250             }
251 0           for my $row (@$res) {
252 0           for my $col (@nums) {
253 0 0         next unless defined($row->{$col});
254 0           $row->{$col} += 0;
255             }
256             }
257 0           return $res;
258             }
259              
260             =back
261              
262             =cut
263              
264             1;
265              
266