File Coverage

blib/lib/DBIx/QuickDB/Driver/PostgreSQL.pm
Criterion Covered Total %
statement 46 147 31.2
branch 10 52 19.2
condition 4 32 12.5
subroutine 11 28 39.2
pod 11 13 84.6
total 82 272 30.1


line stmt bran cond sub pod time code
1             package DBIx::QuickDB::Driver::PostgreSQL;
2 5     5   148535 use strict;
  5         11  
  5         161  
3 5     5   17 use warnings;
  5         6  
  5         256  
4              
5             our $VERSION = '0.000040';
6              
7 5     5   1635 use IPC::Cmd qw/can_run/;
  5         135414  
  5         318  
8 5     5   1353 use DBIx::QuickDB::Util qw/strip_hash_defaults/;
  5         21  
  5         71  
9 5     5   147 use Time::HiRes qw/sleep/;
  5         16  
  5         41  
10 5     5   304 use Scalar::Util qw/reftype/;
  5         29  
  5         246  
11              
12 5     5   25 use parent 'DBIx::QuickDB::Driver';
  5         8  
  5         30  
13              
14 5         26 use DBIx::QuickDB::Util::HashBase qw{
15             -data_dir
16              
17             -initdb -createdb -postgres -psql
18              
19             -config
20             -socket
21             -port
22 5     5   327 };
  5         7  
23              
24             my ($INITDB, $CREATEDB, $POSTGRES, $PSQL, $DBDPG);
25              
26             BEGIN {
27 5     5   20 local $@;
28              
29 5         24 $INITDB = can_run('initdb');
30 5         1182 $CREATEDB = can_run('createdb');
31 5         863 $POSTGRES = can_run('postgres');
32 5         828 $PSQL = can_run('psql');
33 5         938 $DBDPG = eval { require DBD::Pg; 'DBD::Pg'};
  5         7905  
  0         0  
34             }
35              
36             sub version_string {
37 0     0 1 0 my $binary;
38              
39             # Go in reverse order assuming the last param hash provided is most important
40 0         0 for my $arg (reverse @_) {
41 0 0       0 my $type = reftype($arg) or next; # skip if not a ref
42 0 0       0 next unless $type eq 'HASH'; # We have a hashref, possibly blessed
43              
44             # If we find a launcher we are done looping, we want to use this binary.
45 0 0       0 $binary = $arg->{+POSTGRES} and last;
46             }
47              
48             # If no args provided one to use we fallback to the default from $PATH
49 0   0     0 $binary ||= $POSTGRES;
50              
51             # Call the binary with '-V', capturing and returning the output using backticks.
52 0         0 return `$binary -V`;
53             }
54              
55             sub list_env_vars {
56 0     0 1 0 my $self = shift;
57             return (
58 0         0 $self->SUPER::list_env_vars(),
59             qw{
60             PGAPPNAME PGCLIENTENCODING PGCONNECT_TIMEOUT PGDATABASE PGDATESTYLE
61             PGGEQO PGGSSLIB PGHOST PGHOSTADDR PGKRBSRVNAME PGLOCALEDIR
62             PGOPTIONS PGPASSFILE PGPASSWORD PGPORT PGREQUIREPEER PGREQUIRESSL
63             PGSERVICE PGSERVICEFILE PGSSLCERT PGSSLCOMPRESSION PGSSLCRL
64             PGSSLKEY PGSSLMODE PGSSLROOTCERT PGSYSCONFDIR PGTARGETSESSIONATTRS
65             PGTZ PGUSER
66             }
67             );
68             }
69              
70 0     0 0 0 sub error_log { "$_[0]->{+DIR}/error.log" }
71              
72             sub _default_paths {
73             return (
74 7     7   50 initdb => $INITDB,
75             createdb => $CREATEDB,
76             postgres => $POSTGRES,
77             psql => $PSQL,
78             );
79             }
80              
81             sub _default_config {
82 0     0   0 my $self = shift;
83              
84             return (
85             datestyle => "'iso, mdy'",
86             default_text_search_config => "'pg_catalog.english'",
87             lc_messages => "'en_US.UTF-8'",
88             lc_monetary => "'en_US.UTF-8'",
89             lc_numeric => "'en_US.UTF-8'",
90             lc_time => "'en_US.UTF-8'",
91             listen_addresses => "''",
92             log_destination => "'stderr'",
93             logging_collector => "'on'",
94             log_directory => "'$self->{+DIR}'",
95             log_filename => "'error.log'",
96             max_connections => "100",
97             shared_buffers => "128MB",
98             unix_socket_directories => "'$self->{+DIR}'",
99 0         0 port => $self->{+PORT},
100              
101             #dynamic_shared_memory_type => "posix",
102             #log_timezone => "'US/Pacific'",
103             #timezone => "'US/Pacific'",
104             );
105             }
106              
107             sub viable {
108 7     7 1 10 my $this = shift;
109 7         15 my ($spec) = @_;
110              
111 7 50       34 my %check = (ref($this) ? %$this : (), $this->_default_paths, %$spec);
112              
113 7         16 my @bad;
114              
115 7 50       23 push @bad => "'DBD::Pg' module could not be loaded, needed for everything" unless $DBDPG;
116              
117 7 50       24 if ($spec->{bootstrap}) {
118 7 50 33     24 push @bad => "'initdb' command is missing, needed for bootstrap" unless $check{initdb} && -x $check{initdb};
119 7 50 33     32 push @bad => "'createdb' command is missing, needed for bootstrap" unless $check{createdb} && -x $check{createdb};
120             }
121              
122 7 50       22 if ($spec->{autostart}) {
123 7 50 33     19 push @bad => "'postgres' command is missing, needed for autostart" unless $check{postgres} && -x $check{postgres};
124             }
125              
126 7 50       18 if ($spec->{load_sql}) {
127 7 50 33     57 push @bad => "'psql' command is missing, needed for load_sql" unless $check{psql} && -x $check{psql};
128             }
129              
130 7 50       19 return (1, undef) unless @bad;
131 7         42 return (0, join "\n" => @bad);
132             }
133              
134             sub init {
135 0     0 1   my $self = shift;
136 0           $self->SUPER::init();
137              
138 0   0       my $port = $self->{+PORT} ||= '5432';
139              
140 0           my $dir = $self->{+DIR};
141 0           $self->{+DATA_DIR} = "$dir/data";
142 0   0       $self->{+SOCKET} ||= "$dir/.s.PGSQL.$port";
143              
144 0   0       $self->{+ENV_VARS} ||= {};
145 0 0         $self->{+ENV_VARS}->{PGPORT} = $port unless defined $self->{+ENV_VARS}->{PGPORT};
146              
147 0           my %defaults = $self->_default_paths;
148 0   0       $self->{$_} ||= $defaults{$_} for keys %defaults;
149              
150 0           my %cfg_defs = $self->_default_config;
151 0   0       my $cfg = $self->{+CONFIG} ||= {};
152              
153 0           for my $key (keys %cfg_defs) {
154 0 0         next if defined $cfg->{$key};
155 0           $cfg->{$key} = $cfg_defs{$key};
156             }
157             }
158              
159             sub clone_data {
160 0     0 1   my $self = shift;
161              
162 0   0       my $vars = $self->env_vars || {};
163 0 0 0       delete $vars->{PGPORT} if $vars->{PGPORT} && $vars->{PGPORT} eq $self->port;
164              
165             my $config = strip_hash_defaults(
166 0           $self->{+CONFIG},
167             { $self->_default_config },
168             );
169              
170             return (
171 0           $self->SUPER::clone_data(),
172             ENV_VARS() => $vars,
173             CONFIG() => $config,
174             );
175             }
176              
177             sub write_config {
178 0     0 1   my $self = shift;
179              
180 0           my $db_dir = $self->{+DATA_DIR};
181 0 0         open(my $cf, '>', "$db_dir/postgresql.conf") or die "Could not open config file: $!";
182 0           for my $key (sort keys %{$self->{+CONFIG}}) {
  0            
183 0           my $val = $self->{+CONFIG}->{$key};
184 0 0         next unless length($val);
185              
186 0           print $cf "$key = $val\n";
187             }
188 0           close($cf);
189             }
190              
191             sub bootstrap {
192 0     0     my $self = shift;
193              
194 0           my $dir = $self->{+DIR};
195 0           my $db_dir = $self->{+DATA_DIR};
196 0 0         mkdir($db_dir) or die "Could not create data dir: $!";
197 0           $self->run_command([$self->{+INITDB}, '-E', 'UTF8', '-A', 'trust', '-D', $db_dir]);
198              
199 0           $self->write_config;
200 0           $self->start;
201              
202 0           for my $try (1 .. 10) {
203 0           my ($ok, $err);
204             {
205 0           local $@;
  0            
206 0           $ok = eval {
207             $self->catch_startup(sub {
208 0     0     $self->run_command([$self->{+CREATEDB}, '-T', 'template0', '-E', 'UTF8', '-h', $dir, 'quickdb']);
209 0           });
210              
211 0           1;
212             };
213 0           $err = $@;
214             }
215              
216 0 0         last if $ok;
217              
218 0 0         die $err if $try == 5;
219              
220 0           sleep 0.5;
221             }
222              
223 0 0         $self->stop unless $self->{+AUTOSTART};
224              
225 0           return;
226             }
227              
228             sub connect {
229 0     0 1   my $self = shift;
230 0           my ($db_name, %params) = @_;
231              
232 0           my $dbh;
233             $self->catch_startup(sub {
234 0     0     $dbh = $self->SUPER::connect($db_name, %params);
235 0           });
236              
237 0           return $dbh;
238             }
239              
240             sub connect_string {
241 0     0 1   my $self = shift;
242 0           my ($db_name) = @_;
243 0 0         $db_name = 'quickdb' unless defined $db_name;
244              
245 0           my $dir = $self->{+DIR};
246              
247 0           require DBD::Pg;
248 0           return "dbi:Pg:dbname=$db_name;host=$dir"
249             }
250              
251             sub load_sql {
252 0     0 1   my $self = shift;
253 0           my ($dbname, $file) = @_;
254              
255 0           my $dir = $self->{+DIR};
256              
257             $self->catch_startup(sub {
258             $self->run_command([
259 0     0     $self->{+PSQL},
260             '-h' => $dir,
261             '-v' => 'ON_ERROR_STOP=1',
262             '-f' => $file,
263             $dbname,
264             ]);
265 0           });
266             }
267              
268             sub shell_command {
269 0     0 1   my $self = shift;
270 0           my ($db_name) = @_;
271              
272 0           return ($self->{+PSQL}, '-h' => $self->{+DIR}, $db_name);
273             }
274              
275             sub start_command {
276 0     0 1   my $self = shift;
277 0           return ($self->{+POSTGRES}, '-D' => $self->{+DATA_DIR}, '-p' => $self->{+PORT});
278             }
279              
280             sub catch_startup {
281 0     0 0   my $self = shift;
282 0           my ($code) = @_;
283              
284 0           my $start = time;
285 0           while (1) {
286 0           my $waited = time - $start;
287 0 0         die "Timeout waiting for server" if $waited > 10;
288              
289 0           my ($ok, $err, $out);
290             {
291 0           local $@;
  0            
292 0           $ok = eval {
293 0           $out = $code->($self);
294 0           1;
295             };
296              
297 0           $err = $@;
298             }
299              
300 0 0         return $out if $ok;
301              
302 0 0         die $err unless $err =~ m/the database system is starting up/;
303              
304 0           sleep 0.01;
305             }
306             }
307              
308             1;
309              
310             __END__