File Coverage

blib/lib/Module/Build/Database/PostgreSQL.pm
Criterion Covered Total %
statement 36 300 12.0
branch 0 94 0.0
condition 0 47 0.0
subroutine 12 44 27.2
pod 0 6 0.0
total 48 491 9.7


line stmt bran cond sub pod time code
1             =head1 NAME
2              
3             Module::Build::Database::PostgreSQL - PostgreSQL implementation for MBD
4              
5             =head1 SYNOPSIS
6              
7             In Build.PL :
8              
9             my $builder = Module::Build::Database->new(
10             database_type => "PostgreSQL",
11             database_options => {
12             name => "my_database_name",
13             schema => "my_schema_name",
14             # Extra items for scratch databases :
15             append_to_conf => "text to add to postgresql.conf",
16             after_create => q[create schema audit;],
17             },
18             database_extensions => {
19             postgis => { schema => "public", },
20             # directory with postgis.sql and spatial_ref_sys.sql
21             postgis_base => '/usr/local/share/postgresql/contrib'
22             },
23             );
24              
25             =head1 DESCRIPTION
26              
27             Postgres driver for L.
28              
29             =head1 OPTIONS
30              
31             All of the options above may be changed via the Module::Build option
32             handling, e.g.
33              
34             perl Build.PL --database_options name=my_name
35             perl Build.PL --postgis_base=/usr/local/share/postgresql/contrib
36              
37             The options are as follows ;
38              
39             =over 4
40              
41             =item database_options
42              
43             =over 4
44              
45             =item name
46              
47             the name of the database (i.e. 'create database $name')
48              
49             =item schema
50              
51             the name of the schema to be managed by MBD
52              
53             =item append_to_conf
54              
55             extra options to append to C before starting test instances of postgres
56              
57             =item after_create
58              
59             extra SQL to run after running a 'create database' statement. Note that this will be run in several
60             different situations :
61              
62             =over 4
63              
64             =item 1.
65              
66             during a L (creating a test db)
67              
68             =item 2.
69              
70             during a L (also creating a test db)
71              
72             =item 3.
73              
74             during an initial L; when the target database does not yet exist.
75              
76             =back
77              
78             An example of using the after_create statement would be to create a second schema which
79             will not be managed by MBD, but on which the MBD-managed schema depends.
80              
81             =back
82              
83             =item database_extension
84              
85             To specify a server side procedural language you can use the C -E C
86             option, like so:
87              
88             my $builder = Module::Build::Database->new(
89             database_extension => {
90             languages => [ 'plperl', 'pltcl' ],
91             },
92             );
93              
94             Trying to create languages to a patch will not work because they not stored in the main schema and will
95             not be included in C when you run C.
96              
97             This is also similar to
98              
99             after_create => 'create extension ...',
100              
101             except it is executed on B L meaning you can use this to add extensions to
102             existing database deployments.
103              
104             =item postgis_base
105              
106             Specify the directory containing postgis.sql and spatial_ref_sys.sql. If specified these SQL files will be loaded so that
107             you can use PostGIS in your database.
108              
109             =item leave_running
110              
111             If set to true, and if you are not using a persistent scratch database (see next option), then the scratch database will
112             not be stopped and torn down after running C or C.
113              
114             =item scratch_database
115              
116             You can use this option to specify the connection settings for a persistent scratch or temporary database instance, used by
117             the C and C to test schema. B: the C and C
118             will drop and re-create databases on the scratch instance with the same name as the database on your production instance so
119             it is I important that if you use a persistent scratch database that it be dedicated to that task.
120              
121             my $builder = Module::Build::Database->new(
122             scratch_database => {
123             PGHOST => 'databasehost',
124             PGPORT => '5555',
125             PGUSER => 'dbuser',
126             },
127             );
128              
129             If you specify any one of these keys for this option (C, C, C) then MBD will use a persistent
130             scratch database. Any missing values will use the default.
131              
132             You can also specify these settings using environment variables:
133              
134             % export MBD_SCRATCH_PGHOST=databasehost
135             % export MBD_SCRATCH_PGPORT=5555
136             % export MBD_SCRATCH_PGUSER=dbuser
137              
138             By default this module will create its own scratch PostgreSQL instance that uses unix domain sockets for communication
139             each time it needs one when you use the C or C commands. Situations where you might
140             need to use a persistent scratch database:
141              
142             =over 4
143              
144             =item 1.
145              
146             The server and server binaries are hosted on a system different to the one that you are doing development
147              
148             =item 2.
149              
150             You are using MBD on Windows where unix domain sockets are not available
151              
152             =back
153              
154             =back
155              
156             =head1 NOTES
157              
158             The environment variables understood by C:
159             C, C and C will be used when
160             connecting to a live database (for L and
161             L). C will be ignored;
162             the name of the database should be specified in
163             Build.PL instead.
164              
165             =cut
166              
167             package Module::Build::Database::PostgreSQL;
168 4     4   121140 use base 'Module::Build::Database';
  4         6  
  4         1895  
169 4     4   26 use File::Temp qw/tempdir/;
  4         6  
  4         195  
170 4     4   17 use File::Path qw/rmtree/;
  4         6  
  4         136  
171 4     4   17 use File::Basename qw/dirname/;
  4         5  
  4         128  
172 4     4   1783 use File::Copy::Recursive qw/fcopy dirmove/;
  4         8330  
  4         236  
173 4     4   22 use Path::Class qw/file/;
  4         4  
  4         130  
174 4     4   18 use IO::File;
  4         5  
  4         448  
175 4     4   17 use File::Which qw( which );
  4         4  
  4         125  
176              
177 4     4   2294 use Module::Build::Database::PostgreSQL::Templates;
  4         5  
  4         112  
178 4     4   16 use Module::Build::Database::Helpers qw/do_system verify_bin info debug/;
  4         5  
  4         32  
179 4     4   1525 use strict;
  4         7  
  4         99  
180 4     4   15 use warnings;
  4         5  
  4         10847  
181             our $VERSION = '0.56';
182              
183             __PACKAGE__->add_property(database_options => default => { name => "foo", schema => "bar" });
184             __PACKAGE__->add_property(database_extensions => default => { postgis => 0 } );
185             __PACKAGE__->add_property(postgis_base => default => "/usr/local/share/postgis" );
186             __PACKAGE__->add_property(_tmp_db_dir => default => "" );
187             __PACKAGE__->add_property(leave_running => default => 0 ); # leave running after dbtest?
188             __PACKAGE__->add_property(scratch_database => default => { map {; "PG$_" => $ENV{"MBD_SCRATCH_PG$_"} }
189             grep { defined $ENV{"MBD_SCRATCH_PG$_"} }
190             qw( HOST PORT USER ) } );
191              
192             # Binaries used by this module. They should be in $ENV{PATH}.
193             our %Bin = (
194             Psql => 'psql',
195             Pgctl => 'pg_ctl',
196             Postgres => 'postgres',
197             Initdb => 'initdb',
198             Createdb => 'createdb',
199             Dropdb => 'dropdb',
200             Pgdump => 'pg_dump',
201             Pgdoc => [ qw/pg_autodoc postgresql_autodoc/ ],
202             );
203             my $server_bin_dir;
204             if(my $pg_config = which 'pg_config')
205             {
206             $pg_config = Win32::GetShortPathName($pg_config) if $^O eq 'MSWin32' && $pg_config =~ /\s/;
207             $server_bin_dir = `$pg_config --bindir`;
208             chomp $server_bin_dir;
209             $server_bin_dir = Win32::GetShortPathName($server_bin_dir) if $^O eq 'MSWin32' && $server_bin_dir =~ /\s/;
210             undef $server_bin_dir unless -d $server_bin_dir;
211             }
212             verify_bin(\%Bin, $server_bin_dir);
213              
214             sub _do_psql {
215 0     0     my $self = shift;
216 0           my $sql = shift;
217 0           my $database_name = $self->database_options('name');
218 0           my $tmp = File::Temp->new(TEMPLATE => "tmp_db_XXXX", SUFFIX => '.sql');
219 0           print $tmp $sql;
220 0           $tmp->close;
221             # -q: quiet, ON_ERROR_STOP: throw exceptions
222 0           local $ENV{PERL5LIB};
223 0           my $ret = do_system( $Bin{Psql}, "-q", "-vON_ERROR_STOP=1", "-f", "$tmp", $database_name );
224 0           $tmp->unlink_on_destroy($ret);
225 0           $ret;
226             }
227             sub _do_psql_out {
228 0     0     my $self = shift;
229 0           my $sql = shift;
230 0           my $database_name = $self->database_options('name');
231             # -F field separator, -x extended output, -A: unaligned
232 0           local $ENV{PERL5LIB};
233 0           do_system( $Bin{Psql}, "-q", "-vON_ERROR_STOP=1", "-A", "-F ' : '", "-x", "-c", qq["$sql"], $database_name );
234             }
235             sub _do_psql_file {
236 0     0     my $self = shift;
237 0           my $filename = shift;
238 0 0         unless (-e $filename) {
239 0           warn "could not open file $filename";
240 0           return 0;
241             }
242 0 0         unless (-s $filename) {
243 0           warn "file $filename is empty";
244 0           return 0;
245             }
246 0           my $database_name = $self->database_options('name');
247             # -q: quiet, ON_ERROR_STOP: throw exceptions
248 0           local $ENV{PERL5LIB};
249 0           do_system($Bin{Psql},"-q","-vON_ERROR_STOP=1","-f",$filename, $database_name);
250             }
251             sub _do_psql_into_file {
252 0     0     my $self = shift;
253 0           my $filename = shift;
254 0           my $sql = shift;
255 0           my $database_name = $self->database_options('name');
256             # -A: unaligned, -F: field separator, -t: tuples only, ON_ERROR_STOP: throw exceptions
257 0           local $ENV{PERL5LIB};
258 0 0         my $q = $^O eq 'MSWin32' ? '"' : "'";
259 0           do_system( $Bin{Psql}, "-q", "-vON_ERROR_STOP=1", "-A", "-F $q\t$q", "-t", "-c", qq["$sql"], $database_name, ">", "$filename" );
260             }
261             sub _do_psql_capture {
262 0     0     my $self = shift;
263 0           my $sql = shift;
264 0           my $database_name = $self->database_options('name');
265 0           local $ENV{PERL5LIB};
266 0           return qx[$Bin{Psql} -c "$sql" $database_name];
267             }
268              
269             sub _cleanup_old_dbs {
270 0     0     my $self = shift;
271 0           my %args = @_; # pass all => 1 to clean up the current one too
272              
273 0           my $glob;
274             {
275 0           my $tmpdir = tempdir("mbdtest_XXXXXX", TMPDIR => 1);
  0            
276 0           $glob = "$tmpdir";
277 0           rmtree($tmpdir);
278             }
279 0           $glob =~ s/mbdtest_.*$/mbdtest_*/;
280 0           for my $thisdir (glob $glob) {
281 0 0 0       next unless -d $thisdir && -w $thisdir;
282 0           debug "cleaning up old tmp instance : $thisdir";
283 0           $self->_stop_db("$thisdir/db");
284 0           rmtree($thisdir);
285             }
286             }
287              
288             sub _start_new_db {
289 0     0     my $self = shift;
290             # Start a new database and return the host on which it was started.
291              
292 0           my $database_name = $self->database_options('name');
293 0           $ENV{PGDATABASE} = $database_name;
294              
295 0 0         if(%{ $self->scratch_database }) {
  0            
296 0           delete @ENV{qw( PGHOST PGUSER PGPORT )};
297 0           %ENV = (%ENV, %{ $self->scratch_database });
  0            
298 0           do_system("_silent", $Bin{Dropdb}, $database_name);
299              
300             } else {
301              
302 0           $self->_cleanup_old_dbs();
303              
304 0           my $tmpdir = tempdir("mbdtest_XXXXXX", TMPDIR => 1);
305 0           my $dbdir = $tmpdir."/db";
306 0           my $initlog = "$tmpdir/postgres.log";
307 0           $self->_tmp_db_dir($dbdir);
308              
309 0           $ENV{PGHOST} = "$dbdir"; # makes psql use a socket, not a tcp port
310 0           delete $ENV{PGUSER};
311 0           delete $ENV{PGPORT};
312              
313 0           debug "initializing database (log: $initlog)";
314              
315 0 0         do_system($Bin{Initdb}, "-D", "$dbdir", ">>", "$initlog", "2>&1") or die "could not initdb";
316              
317 0 0         if (my $conf_append = $self->database_options('append_to_conf')) {
318 0 0         die "cannot find postgresql.conf" unless -e "$dbdir/postgresql.conf";
319 0 0         open my $fp, ">> $dbdir/postgresql.conf" or die "could not open postgresql.conf : $!";
320 0           print $fp $conf_append;
321 0           close $fp;
322             }
323              
324 0           my $pmopts = qq[-k $dbdir -h '' -p 5432];
325              
326 0           debug "# starting postgres in $dbdir";
327 0 0         do_system($Bin{Pgctl}, qq[-o "$pmopts"], "-w", "-t", 120, "-D", "$dbdir", "-l", "postmaster.log", "start") or die "could not start postgres";
328              
329 0           my $domain = $dbdir.'/.s.PGSQL.5432';
330 0 0         -e $domain or die "could not find $domain";
331             }
332              
333 0           $self->_create_database();
334              
335 0           return $self->_dbhost;
336             }
337              
338             sub _remove_db {
339 0     0     my $self = shift;
340 0 0 0       return if $ENV{MBD_DONT_STOP_TEST_DB} || %{ $self->scratch_database };
  0            
341 0   0       my $dbdir = shift || $self->_tmp_db_dir();
342 0           $dbdir =~ s/\/db$//;
343 0           rmtree $dbdir;
344             }
345              
346             sub _stop_db {
347 0     0     my $self = shift;
348 0 0 0       return if $ENV{MBD_DONT_STOP_TEST_DB} || %{ $self->scratch_database };
  0            
349 0   0       my $dbdir = shift || $self->_tmp_db_dir();
350 0           my $pid_file = "$dbdir/postmaster.pid";
351 0 0         unless (-e $pid_file) {
352 0           debug "no pid file ($pid_file), not stopping db";
353 0           return;
354             }
355 0           my ($pid) = IO::File->new("<$pid_file")->getlines;
356 0           chomp $pid;
357 0           kill "TERM", $pid;
358 0           sleep 1;
359 0 0         return unless kill 0, $pid;
360 0 0         kill 9, $pid or info "could not send signal to $pid";
361             }
362              
363             sub _apply_base_sql {
364 0     0     my $self = shift;
365 0   0       my $filename = shift || $self->base_dir."/db/dist/base.sql";
366 0 0         return unless -e $filename;
367 0           info "applying base.sql";
368 0           $self->_do_psql_file($filename);
369             }
370              
371             sub _apply_base_data {
372 0     0     my $self = shift;
373 0   0       my $filename = shift || $self->base_dir."/db/dist/base_data.sql";
374 0 0         return 1 unless -e $filename;
375 0           info "applying base_data.sql";
376 0           $self->_do_psql_file($filename);
377             }
378              
379             sub _dump_base_sql {
380             # Optional parameter "outfile" gives the name of the file into which to dump the schema.
381             # If the parameter is omitted, dump and atomically rename to db/dist/base.sql.
382 0     0     my $self = shift;
383 0           my %args = @_;
384 0   0       my $outfile = $args{outfile} || $self->base_dir. "/db/dist/base.sql";
385              
386 0           my $tmpfile = file( tempdir( CLEANUP => 1 ), 'dump.sql');
387              
388             # -x : no privileges, -O : no owner, -s : schema only, -n : only this schema
389 0           my $database_schema = $self->database_options('schema');
390 0           my $database_name = $self->database_options('name');
391 0           local $ENV{PERL5LIB};
392             do_system( $Bin{Pgdump}, "-xOs", "-E", "utf8", "-n", $database_schema, $database_name, ">", $tmpfile )
393 0 0         or do {
394 0           info "Error running pgdump";
395 0           die "Error running pgdump : $! ${^CHILD_ERROR_NATIVE}";
396 0           return 0;
397             };
398              
399 0           my @lines = $tmpfile->slurp();
400 0 0         unless (@lines) {
401 0           die "# Could not run pgdump and write to $tmpfile";
402             }
403 0   0       @lines = grep {
404 0           $_ !~ /^--/
405             and $_ !~ /^CREATE SCHEMA $database_schema;$/
406             and $_ !~ /^SET (search_path|lock_timeout)/
407             } @lines;
408 0           for (@lines) {
409 0 0         /alter table/i and s/$database_schema\.//;
410             }
411 0           file($outfile)->spew(join '', @lines);
412 0 0 0       if (@lines > 0 && !-s $outfile) {
413 0           die "# Unable to write to $outfile";
414             }
415 0           return 1;
416             }
417              
418             sub _dump_base_data {
419             # Optional parameter "outfile, defaults to db/dist/base_data.sql
420 0     0     my $self = shift;
421 0           my %args = @_;
422 0   0       my $outfile = $args{outfile} || $self->base_dir. "/db/dist/base_data.sql";
423              
424 0           my $tmpfile = File::Temp->new(
425             TEMPLATE => (dirname $outfile)."/dump_XXXXXX",
426             UNLINK => 0
427             );
428 0           $tmpfile->close;
429              
430             # -x : no privileges, -O : no owner, -s : schema only, -n : only this schema
431 0           my $database_schema = $self->database_options('schema');
432 0           my $database_name = $self->database_options('name');
433 0           local $ENV{PERL5LIB};
434 0 0         do_system( $Bin{Pgdump}, "--data-only", "-xO", "-E", "utf8", "-n", $database_schema, $database_name,
435             "|", "egrep -v '^SET (lock_timeout|search_path)'",
436             ">", "$tmpfile" )
437             or return 0;
438 0 0         rename "$tmpfile", $outfile or die "rename failed: $!";
439             }
440              
441             sub _apply_patch {
442 0     0     my $self = shift;
443 0           my $patch_file = shift;
444              
445 0           return $self->_do_psql_file($self->base_dir."/db/patches/$patch_file");
446             }
447              
448             sub _is_fresh_install {
449 0     0     my $self = shift;
450              
451 0           my $database_name = $self->database_options('name');
452 0 0         unless ($self->_database_exists) {
453 0           info "database $database_name does not exist";
454 0           return 1;
455             }
456              
457 0           my $file = File::Temp->new(); $file->close;
  0            
458 0           my $database_schema = $self->database_options('schema');
459 0           $self->_do_psql_into_file("$file","\\dn $database_schema");
460 0           return !do_system("_silent","grep -q $database_schema $file");
461             }
462              
463             sub _show_live_db {
464             # Display the connection information
465 0     0     my $self = shift;
466              
467 0   0       info "PGUSER : " . ( $ENV{PGUSER} || "" );
468 0   0       info "PGHOST : " . ( $ENV{PGHOST} || "" );
469 0   0       info "PGPORT : " . ( $ENV{PGPORT} || "" );
470              
471 0   0       my $database_name = shift || $self->database_options('name');
472 0           info "database : $database_name";
473              
474 0 0         return unless $self->_database_exists;
475 0           $self->_do_psql_out("select current_database(),session_user,version();");
476             }
477              
478             sub _patch_table_exists {
479             # returns true or false
480 0     0     my $self = shift;
481 0           my $file = File::Temp->new(); $file->close;
  0            
482 0           my $database_schema = $self->database_options('schema');
483 0           $self->_do_psql_into_file("$file","select tablename from pg_tables where tablename='patches_applied' and schemaname = '$database_schema'");
484 0           return do_system("_silent","grep -q patches_applied $file");
485             }
486              
487             sub _dump_patch_table {
488             # Dump the patch table in an existing db into a flat file, that
489             # will be in the same format as patches_applied.txt.
490 0     0     my $self = shift;
491 0           my %args = @_;
492 0 0         my $filename = $args{outfile} or Carp::confess "need a filename";
493 0           my $database_schema = $self->database_options('schema');
494 0           $self->_do_psql_into_file($filename,"select patch_name,patch_md5 from $database_schema.patches_applied order by patch_name");
495             }
496              
497             sub _create_patch_table {
498 0     0     my $self = shift;
499             # create a new patch table
500 0           my $database_schema = $self->database_options('schema');
501 0           my $sql = <
502             CREATE TABLE $database_schema.patches_applied (
503             patch_name varchar(255) primary key,
504             patch_md5 varchar(255),
505             when_applied timestamp );
506             EOSQL
507 0           $self->_do_psql($sql);
508             }
509              
510             sub _insert_patch_record {
511 0     0     my $self = shift;
512 0           my $record = shift;
513 0           my ($name,$md5) = @$record;
514 0           my $database_schema = $self->database_options('schema');
515 0           $self->_do_psql("insert into $database_schema.patches_applied (patch_name, patch_md5, when_applied) ".
516             " values ('$name','$md5',now()) ");
517             }
518              
519             sub _database_exists {
520 0     0     my $self = shift;
521 0   0       my $database_name = shift || $self->database_options('name');
522 0           local $ENV{PERL5LIB};
523 0           scalar grep /^$database_name$/, map { [split /:/]->[0] } `psql -Alt -F:`;
  0            
524             }
525              
526             sub _create_language_extensions {
527 0     0     my $self = shift;
528 0           my $list = $self->database_extensions('languages');
529 0 0         return unless $list;
530 0           foreach my $lang (@$list) {
531 0 0         $self->_do_psql("create extension if not exists $lang") || die "error creating language: $lang";
532             }
533             }
534              
535             sub _create_database {
536 0     0     my $self = shift;
537              
538 0           my $database_name = $self->database_options('name');
539 0           my $database_schema = $self->database_options('schema');
540              
541             # create the database if necessary
542 0 0         unless ($self->_database_exists($database_name)) {
543 0           local $ENV{PERL5LIB};
544 0 0         do_system($Bin{Createdb}, $database_name) or die "could not createdb";
545             }
546              
547             # Create a fresh schema in the database.
548 0 0         $self->_do_psql("create schema $database_schema") unless $database_schema eq 'public';
549              
550 0           $self->_do_psql("alter database $database_name set client_min_messages to ERROR");
551              
552 0           $self->_do_psql("alter database $database_name set search_path to $database_schema;");
553              
554             # stolen from http://wiki.postgresql.org/wiki/CREATE_OR_REPLACE_LANGUAGE
555 0           $self->_do_psql(<<'SAFE_MAKE_PLPGSQL');
556             CREATE OR REPLACE FUNCTION make_plpgsql()
557             RETURNS VOID
558             LANGUAGE SQL
559             AS $$
560             CREATE LANGUAGE plpgsql;
561             $$;
562              
563             SELECT
564             CASE
565             WHEN EXISTS(
566             SELECT 1
567             FROM pg_catalog.pg_language
568             WHERE lanname='plpgsql'
569             )
570             THEN NULL
571             ELSE make_plpgsql() END;
572              
573             DROP FUNCTION make_plpgsql();
574             SAFE_MAKE_PLPGSQL
575              
576 0 0         if (my $postgis = $self->database_extensions('postgis')) {
577 0           info "applying postgis extension";
578 0 0         my $postgis_schema = $postgis->{schema} or die "No schema given for postgis";
579 0 0         $self->_do_psql("create schema $postgis_schema") unless $postgis_schema eq 'public';
580 0           $self->_do_psql("alter database $database_name set search_path to $postgis_schema;");
581             # We need to run "createlang plpgsql" first.
582 0 0         $self->_do_psql_file($self->postgis_base. "/postgis.sql") or die "could not do postgis.sql";
583 0 0         $self->_do_psql_file($self->postgis_base. "/spatial_ref_sys.sql") or die "could not do spatial_ref_sys.sql";
584 0           $self->_do_psql("alter database $database_name set search_path to $database_schema, $postgis_schema");
585             }
586              
587 0 0         if (my $sql = $self->database_options('post_initdb')) {
588 0           info "applying post_initdb (nb: this option has been renamed to 'after_create')";
589 0           $self->_do_psql($sql);
590             }
591              
592 0 0         if (my $sql = $self->database_options('after_create')) {
593 0           info "applying after_create";
594 0           $self->_do_psql($sql);
595             }
596              
597 0           1;
598             }
599              
600             sub _remove_patches_applied_table {
601 0     0     my $self = shift;
602 0           my $database_schema = $self->database_options('schema');
603 0           $self->_do_psql("drop table if exists $database_schema.patches_applied;");
604             }
605              
606             sub _generate_docs {
607 0     0     my $self = shift;
608 0           my %args = @_;
609 0 0         my $dir = $args{dir} or die "missing dir";
610 0           my $tmpdir = tempdir;
611 0           my $tc = "Module::Build::Database::PostgreSQL::Templates";
612 0           my $database_name = $self->database_options('name');
613 0           my $database_schema = $self->database_options('schema');
614              
615 0           $self->_start_new_db();
616 0           $self->_apply_base_sql();
617              
618 0           chdir $tmpdir;
619 0           for my $filename ($tc->filenames) {
620 0 0         open my $fp, ">$filename" or die $!;
621 0           print ${fp} $tc->file_contents($filename);
622 0           close $fp;
623             }
624              
625             # http://perlmonks.org/?node_id=821413
626 0           do_system( $Bin{Pgdoc}, "-d", $database_name, "-s", $database_schema, "-l .", "-t pod" );
627 0           do_system( $Bin{Pgdoc}, "-d", $database_name, "-s", $database_schema, "-l .", "-t html" );
628 0           do_system( $Bin{Pgdoc}, "-d", $database_name, "-s", $database_schema, "-l .", "-t dot" );
629              
630 0           for my $type (qw(pod html)) {
631 0 0         my $fp = IO::File->new("<$database_name.$type") or die $!;
632 0 0         mkdir $type or die $!;
633 0           my $outfp;
634 0           while (<$fp>) {
635 0 0         s/^_CUT: (.*)$// and do { $outfp = IO::File->new(">$type/$1") or die $!; };
  0 0          
636 0 0         s/^_DB: (.*)$// and do { $_ = $self->_do_psql_capture($1); s/^/ /gm; };
  0            
  0            
637 0 0         print ${outfp} $_ if defined($outfp);
638             }
639             }
640 0           dirmove "$tmpdir/pod", "$dir/pod";
641 0           info "Generated $dir/pod";
642 0           dirmove "$tmpdir/html", "$dir/html";
643 0           info "Generated $dir/html";
644 0           fcopy "$tmpdir/$database_name.dot", "$dir";
645 0           info "Generated $dir/$database_name.dot";
646             }
647              
648 0     0 0   sub ACTION_dbtest { shift->SUPER::ACTION_dbtest(@_); }
649 0     0 0   sub ACTION_dbclean { shift->SUPER::ACTION_dbclean(@_); }
650 0     0 0   sub ACTION_dbdist { shift->SUPER::ACTION_dbdist(@_); }
651 0     0 0   sub ACTION_dbdocs { shift->SUPER::ACTION_dbdocs(@_); }
652 0     0 0   sub ACTION_dbinstall { shift->SUPER::ACTION_dbinstall(@_); }
653 0     0 0   sub ACTION_dbfakeinstall { shift->SUPER::ACTION_dbfakeinstall(@_); }
654              
655             sub _dbhost {
656 0   0 0     return $ENV{PGHOST} || 'localhost';
657             }
658              
659             1;
660