File Coverage

blib/lib/DBIx/QuickDB/Driver/PostgreSQL.pm
Criterion Covered Total %
statement 46 146 31.5
branch 10 52 19.2
condition 4 32 12.5
subroutine 11 27 40.7
pod 11 12 91.6
total 82 269 30.4


line stmt bran cond sub pod time code
1             package DBIx::QuickDB::Driver::PostgreSQL;
2 5     5   228828 use strict;
  5         19  
  5         161  
3 5     5   29 use warnings;
  5         9  
  5         237  
4              
5             our $VERSION = '0.000021';
6              
7 5     5   2161 use IPC::Cmd qw/can_run/;
  5         153933  
  5         318  
8 5     5   1878 use DBIx::QuickDB::Util qw/strip_hash_defaults/;
  5         15  
  5         54  
9 5     5   192 use Time::HiRes qw/sleep/;
  5         11  
  5         56  
10 5     5   818 use Scalar::Util qw/reftype/;
  5         15  
  5         274  
11              
12 5     5   576 use parent 'DBIx::QuickDB::Driver';
  5         321  
  5         54  
13              
14 5         29 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   332 };
  5         25  
23              
24             my ($INITDB, $CREATEDB, $POSTGRES, $PSQL, $DBDPG);
25              
26             BEGIN {
27 5     5   21 local $@;
28              
29 5         28 $INITDB = can_run('initdb');
30 5         1860 $CREATEDB = can_run('createdb');
31 5         1679 $POSTGRES = can_run('postgres');
32 5         1635 $PSQL = can_run('psql');
33 5         1634 $DBDPG = eval { require DBD::Pg; 'DBD::Pg'};
  5         9330  
  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 if $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             sub _default_paths {
71             return (
72 7     7   59 initdb => $INITDB,
73             createdb => $CREATEDB,
74             postgres => $POSTGRES,
75             psql => $PSQL,
76             );
77             }
78              
79             sub _default_config {
80 0     0   0 my $self = shift;
81              
82             return (
83             datestyle => "'iso, mdy'",
84             default_text_search_config => "'pg_catalog.english'",
85             lc_messages => "'en_US.UTF-8'",
86             lc_monetary => "'en_US.UTF-8'",
87             lc_numeric => "'en_US.UTF-8'",
88             lc_time => "'en_US.UTF-8'",
89             listen_addresses => "''",
90             max_connections => "100",
91             shared_buffers => "128MB",
92             unix_socket_directories => "'$self->{+DIR}'",
93 0         0 port => $self->{+PORT},
94              
95             #dynamic_shared_memory_type => "posix",
96             #log_timezone => "'US/Pacific'",
97             #timezone => "'US/Pacific'",
98             );
99             }
100              
101             sub viable {
102 7     7 1 21 my $this = shift;
103 7         19 my ($spec) = @_;
104              
105 7 50       37 my %check = (ref($this) ? %$this : (), $this->_default_paths, %$spec);
106              
107 7         19 my @bad;
108              
109 7 50       26 push @bad => "'DBD::Pg' module could not be loaded, needed for everything" unless $DBDPG;
110              
111 7 50       26 if ($spec->{bootstrap}) {
112 7 50 33     41 push @bad => "'initdb' command is missing, needed for bootstrap" unless $check{initdb} && -x $check{initdb};
113 7 50 33     36 push @bad => "'createdb' command is missing, needed for bootstrap" unless $check{createdb} && -x $check{createdb};
114             }
115              
116 7 50       24 if ($spec->{autostart}) {
117 7 50 33     27 push @bad => "'postgres' command is missing, needed for autostart" unless $check{postgres} && -x $check{postgres};
118             }
119              
120 7 50       20 if ($spec->{load_sql}) {
121 7 50 33     25 push @bad => "'psql' command is missing, needed for load_sql" unless $check{psql} && -x $check{psql};
122             }
123              
124 7 50       22 return (1, undef) unless @bad;
125 7         52 return (0, join "\n" => @bad);
126             }
127              
128             sub init {
129 0     0 1   my $self = shift;
130 0           $self->SUPER::init();
131              
132 0   0       my $port = $self->{+PORT} ||= '5432';
133              
134 0           my $dir = $self->{+DIR};
135 0           $self->{+DATA_DIR} = "$dir/data";
136 0   0       $self->{+SOCKET} ||= "$dir/.s.PGSQL.$port";
137              
138 0   0       $self->{+ENV_VARS} ||= {};
139 0 0         $self->{+ENV_VARS}->{PGPORT} = $port unless defined $self->{+ENV_VARS}->{PGPORT};
140              
141 0           my %defaults = $self->_default_paths;
142 0   0       $self->{$_} ||= $defaults{$_} for keys %defaults;
143              
144 0           my %cfg_defs = $self->_default_config;
145 0   0       my $cfg = $self->{+CONFIG} ||= {};
146              
147 0           for my $key (keys %cfg_defs) {
148 0 0         next if defined $cfg->{$key};
149 0           $cfg->{$key} = $cfg_defs{$key};
150             }
151             }
152              
153             sub clone_data {
154 0     0 1   my $self = shift;
155              
156 0   0       my $vars = $self->env_vars || {};
157 0 0 0       delete $vars->{PGPORT} if $vars->{PGPORT} && $vars->{PGPORT} eq $self->port;
158              
159             my $config = strip_hash_defaults(
160 0           $self->{+CONFIG},
161             { $self->_default_config },
162             );
163              
164             return (
165 0           $self->SUPER::clone_data(),
166             ENV_VARS() => $vars,
167             CONFIG() => $config,
168             );
169             }
170              
171             sub write_config {
172 0     0 1   my $self = shift;
173              
174 0           my $db_dir = $self->{+DATA_DIR};
175 0 0         open(my $cf, '>', "$db_dir/postgresql.conf") or die "Could not open config file: $!";
176 0           for my $key (sort keys %{$self->{+CONFIG}}) {
  0            
177 0           my $val = $self->{+CONFIG}->{$key};
178 0 0         next unless length($val);
179              
180 0           print $cf "$key = $val\n";
181             }
182 0           close($cf);
183             }
184              
185             sub bootstrap {
186 0     0     my $self = shift;
187              
188 0           my $dir = $self->{+DIR};
189 0           my $db_dir = $self->{+DATA_DIR};
190 0 0         mkdir($db_dir) or die "Could not create data dir: $!";
191 0           $self->run_command([$self->{+INITDB}, '-E', 'UTF8', '-D', $db_dir]);
192              
193 0           $self->write_config;
194 0           $self->start;
195              
196 0           for my $try (1 .. 10) {
197 0           my ($ok, $err);
198             {
199 0           local $@;
  0            
200 0           $ok = eval {
201             $self->catch_startup(sub {
202 0     0     $self->run_command([$self->{+CREATEDB}, '-T', 'template0', '-E', 'UTF8', '-h', $dir, 'quickdb']);
203 0           });
204              
205 0           1;
206             };
207 0           $err = $@;
208             }
209              
210 0 0         last if $ok;
211              
212 0 0         die $@ if $try == 5;
213              
214 0           sleep 0.5;
215             }
216              
217 0 0         $self->stop unless $self->{+AUTOSTART};
218              
219 0           return;
220             }
221              
222             sub connect {
223 0     0 1   my $self = shift;
224 0           my ($db_name, %params) = @_;
225              
226 0           my $dbh;
227             $self->catch_startup(sub {
228 0     0     $dbh = $self->SUPER::connect($db_name, %params);
229 0           });
230              
231 0           return $dbh;
232             }
233              
234             sub connect_string {
235 0     0 1   my $self = shift;
236 0           my ($db_name) = @_;
237 0 0         $db_name = 'quickdb' unless defined $db_name;
238              
239 0           my $dir = $self->{+DIR};
240              
241 0           require DBD::Pg;
242 0           return "dbi:Pg:dbname=$db_name;host=$dir"
243             }
244              
245             sub load_sql {
246 0     0 1   my $self = shift;
247 0           my ($dbname, $file) = @_;
248              
249 0           my $dir = $self->{+DIR};
250              
251             $self->catch_startup(sub {
252             $self->run_command([
253 0     0     $self->{+PSQL},
254             '-h' => $dir,
255             '-v' => 'ON_ERROR_STOP=1',
256             '-f' => $file,
257             $dbname,
258             ]);
259 0           });
260             }
261              
262             sub shell_command {
263 0     0 1   my $self = shift;
264 0           my ($db_name) = @_;
265              
266 0           return ($self->{+PSQL}, '-h' => $self->{+DIR}, $db_name);
267             }
268              
269             sub start_command {
270 0     0 1   my $self = shift;
271 0           return ($self->{+POSTGRES}, '-D' => $self->{+DATA_DIR}, '-p' => $self->{+PORT});
272             }
273              
274             sub catch_startup {
275 0     0 0   my $self = shift;
276 0           my ($code) = @_;
277              
278 0           my $start = time;
279 0           while (1) {
280 0           my $waited = time - $start;
281 0 0         die "Timeout waiting for server" if $waited > 10;
282              
283 0           my ($ok, $err, $out);
284             {
285 0           local $@;
  0            
286 0           $ok = eval {
287 0           $out = $code->($self);
288 0           1;
289             };
290              
291 0           $err = $@;
292             }
293              
294 0 0         return $out if $ok;
295              
296 0 0         die $err unless $err =~ m/the database system is starting up/;
297              
298 0           sleep 0.01;
299             }
300             }
301              
302             1;
303              
304             __END__