File Coverage

blib/lib/Rose/Planter/DB.pm
Criterion Covered Total %
statement 7 9 77.7
branch n/a
condition n/a
subroutine 3 3 100.0
pod n/a
total 10 12 83.3


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   31341 use Log::Log4perl qw /:easy/;
  2         151722  
  2         18  
17              
18 2     2   3556 eval { use Module::Build::Database; };
  2         462981  
  2         63  
19 2     2   2463 use DateTime::Format::Pg;
  0            
  0            
20             use DBIx::Connector;
21             use List::MoreUtils qw/mesh/;
22              
23             use base 'Rose::DB';
24              
25             use strict;
26             use warnings;
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             my $d = shift;
42             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             my $self = shift;
55             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             $ENV{PGTZ} = "UTC";
59             $connections{$class} ||= DBIx::Connector->new(@_);
60             # See http://archives.postgresql.org/pgsql-performance/2011-02/msg00493.php
61             $connections{$class}->dbh->{pg_server_prepare} = 0;
62             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             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             my $class = shift;
110             my %args = @_;
111             my $module_name = $args{module_name} or die "no module name passed";
112             my $conf = $args{conf};
113             my $register_params = $args{register_params} || {};
114             my $mbd = $ENV{HARNESS_ACTIVE}
115             && Module::Build::Database->can('current')
116             && -d './_build'
117             ? Module::Build::Database->current : undef;
118             my $we_are_testing = ( $mbd && $mbd->module_name eq $module_name );
119             my $live_env_var = ( uc $module_name ) . '_LIVE';
120             my $we_are_live = $ENV{$live_env_var} ? 1 : 0;
121             die "no conf argument passed" if !$conf && !$we_are_testing;
122              
123             $Registered{$module_name} = (ref $class || $class);
124             my %default = (
125             type => "main",
126             driver => "Pg",
127             connect_options => {
128             PrintError => 1,
129             RaiseError => 0,
130             },
131             %$register_params,
132             );
133              
134             $class->default_type("main");
135              
136             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             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             my %opts = %{ $mbd->can('database_options') ? $mbd->database_options : {} };
142             if ($opts{name}) {
143             $opts{database} = delete $opts{name};
144             };
145             $opts{host} = $mbd->notes("dbtest_host") if $mbd->notes("dbtest_host");
146             # sanitize these, since MBD sanitizes when it starts a database
147             delete $ENV{PGPORT};
148             delete $ENV{PGUSER};
149             $class->register_db( %default, %opts, domain => "test" );
150             $class->default_domain("test");
151             return;
152             }
153              
154             # Just use "db" for the settings.
155             if ($conf->db(default => '')) {
156             my $domain = $we_are_live ? "live" : "dev";
157             eval {
158             $class->register_db( %default, domain => $domain, $conf->db );
159             };
160             warn "Error registering database : $@" if $@;
161             $class->default_domain($domain);
162             return;
163             }
164              
165             warn "'db' may now be used instead of 'databases->dev' in the configuration file.";
166             # Old way, for backwards compatibility.
167             unless ($conf->databases(is_defined => 1)) {
168             warn "No databases defined in configuration file.";
169             $conf->databases(default => {});
170             }
171              
172             warn "No dev database was defined in the configuration file.\n" unless $conf->databases->dev(is_defined => 1);
173             $conf->databases->dev(default => {});
174              
175             $class->register_db( %default, domain => "dev", $conf->databases->dev ) if $conf->databases->dev(is_defined => 1);
176             $class->register_db( %default, domain => "live", $conf->databases->live ) if $conf->databases->live(is_defined => 1);
177              
178             $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             my $class = shift;
190             my $module_name = shift or die "missing required parameter module_name";
191             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             my $class = shift;
202              
203             LOGDIE "Will not load golden dataset unless the database domain is test or dev"
204             unless $class->domain =~ /^(dev|test)$/;
205              
206             INFO "Loading golden dataset, domain : ".$class->domain;
207             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             my $self = shift;
221             my $table = shift;
222             return 1 if $table =~ /^v_/;
223             $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             my $class = shift;
234             my $sql = shift;
235             my @bind = @_;
236             my $obj = (ref $class ? $class : $class->new_or_cached);
237             my $sth = $obj->dbh->prepare($sql);
238             $sth->execute(@bind) or die $sth->errstr;
239             my $types = $sth->{'pg_type'};
240             my $names = $sth->{'NAME'};
241             my $res = $sth->fetchall_arrayref({});
242             return $res unless ref $names && ref $types;
243              
244             # Force all bigints into numeric context for JSON. (see JSON::XS)
245             my %name2type = mesh @$names, @$types;
246             return $res unless grep /int8/, @$types;
247             my @nums;
248             for (@$names) {
249             push @nums, $_ if $name2type{$_} eq 'int8';
250             }
251             for my $row (@$res) {
252             for my $col (@nums) {
253             next unless defined($row->{$col});
254             $row->{$col} += 0;
255             }
256             }
257             return $res;
258             }
259              
260             =back
261              
262             =cut
263              
264             1;
265              
266