File Coverage

blib/lib/Database/Temp/Driver/Pg.pm
Criterion Covered Total %
statement 23 107 21.5
branch 1 14 7.1
condition 0 5 0.0
subroutine 7 18 38.8
pod 2 2 100.0
total 33 146 22.6


line stmt bran cond sub pod time code
1             package Database::Temp::Driver::Pg;
2              
3 1     1   936 use strict;
  1         2  
  1         45  
4 1     1   9 use warnings;
  1         2  
  1         88  
5              
6             # ABSTRACT: Create an ad-hoc database, driver for Postgres
7              
8             our $VERSION = '0.003'; # VERSION: generated by DZP::OurPkgVersion
9              
10 1     1   702 use Module::Load::Conditional qw( can_load );
  1         38816  
  1         99  
11              
12 1     1   12 use DBI;
  1         2  
  1         55  
13 1     1   6 use Log::Any;
  1         2  
  1         12  
14 1     1   75 use Try::Tiny;
  1         3  
  1         17840  
15              
16             sub is_available {
17 1     1 1 60 my $_log = Log::Any->get_logger( category => 'Database::Temp' );
18 1         3502 my %needed = ( 'DBD::Pg' => 1.41_01, );
19 1 50       6 if ( !can_load( modules => \%needed ) ) {
20 1         1123 $_log->infof( 'Cannot load module %s, %s', %needed );
21 1         44 return 0;
22             }
23 0           my $dbh;
24 0           my $ok = 1;
25             try {
26 0     0     $dbh = _open_dbh( _main_db_connection() );
27 0           $dbh->disconnect();
28 0           1;
29             }
30             catch {
31 0     0     my $error = $_;
32 0           $_log->errorf( 'Cannot connect to main db connection. Error: %s', $error );
33 0           $ok = 0;
34 0           };
35 0 0         return 0 if ( !$ok );
36 0           return 1;
37             }
38              
39             sub _main_db_connection {
40             return {
41 0     0     'dsn' => _compile_dsn( 'postgres', undef, undef, 'Database--Temp' ),
42             'username' => undef,
43             'password' => undef,
44             'attr' => {
45             'AutoCommit' => 1,
46             'RaiseError' => 1,
47             'PrintError' => 1,
48             'pg_server_prepare' => 0,
49             'TraceLevel' => 0,
50             },
51             };
52             }
53              
54             sub _open_dbh {
55 0     0     my ($db_connection) = @_;
56             return DBI->connect( $db_connection->{'dsn'}, $db_connection->{'username'}, $db_connection->{'password'},
57 0           $db_connection->{'attr'} );
58             }
59              
60             sub _create {
61 0     0     my ($name) = @_;
62 0           my $_log = Log::Any->get_logger( category => 'Database::Temp' );
63              
64 0           my $temp_db_name = $name;
65 0           my $temp_user_name = $name;
66 0           my $main_dbh = _open_dbh(_main_db_connection);
67 0           $main_dbh->do("CREATE ROLE \"$temp_user_name\" LOGIN ENCRYPTED PASSWORD '$temp_user_name'");
68 0           $_log->debugf( 'Created test user \'%s\'', $temp_user_name );
69 0           $main_dbh->do("CREATE DATABASE \"$temp_db_name\" OWNER \"$temp_user_name\"");
70 0           $_log->debugf( 'Created temp database \'%s\'', $temp_db_name );
71              
72             # $main_dbh->do("GRANT ALL ON DATABASE \"$temp_db_name\" TO \"$temp_db_name\"");
73 0           $main_dbh->disconnect();
74 0           return;
75             }
76              
77             sub _compile_dsn {
78 0     0     my ( $name, $host, $port, $app_name ) = @_;
79 0           my $dsn;
80 0           my $driver = ( __PACKAGE__ =~ m/::( [[:alnum:]]{1,}) $/msx )[0];
81 0 0 0       if ( $host && $port ) {
82 0           $dsn = sprintf 'dbi:%s:dbname=%s;host=%s;port=%s;application_name=%s', $driver, $name, $host, $port, $app_name;
83             }
84             else {
85 0           $dsn = sprintf 'dbi:%s:dbname=%s;application_name=%s', $driver, $name, $app_name;
86             }
87 0           return $dsn;
88             }
89              
90             sub new {
91 0     0 1   my ( $class, %params ) = @_;
92 0           my $_log = Log::Any->get_logger( category => 'Database::Temp' );
93              
94 0           my $name = $params{'name'};
95 0           _create($name);
96 0           my $dsn = _compile_dsn( $name, 'localhost', '5432', 'DBHandle' );
97 0           $_log->debugf( 'Created temp database \'%s\'', $name );
98              
99 0           my %attrs = (
100              
101             # 'ReadOnly' => 0,
102             'AutoCommit' => 1,
103             'RaiseError' => 1,
104             'PrintError' => 1,
105             'RaiseWarn' => 1,
106             'PrintWarn' => 1,
107             'TaintIn' => 1,
108             'TaintOut' => 0,
109             'TraceLevel' => 0,
110             'pg_server_prepare' => 0,
111             );
112 0           my %info = ();
113              
114             # Construct start method
115             my $_start = sub {
116             ## no critic (Variables::ProhibitReusedNames)
117 0     0     my ( $dbh, $name ) = @_;
118 0           my $_log = Log::Any->get_logger( category => 'Database::Temp' );
119 0           $_log->debugf( 'Created temp db \'%s\'', $name );
120 0           };
121              
122             # Construct init method
123 0           my $init;
124 0 0         if ( ref $params{'init'} eq 'CODE' ) {
125 0           $init = $params{'init'};
126             }
127             else { # SCALAR
128             $init = sub {
129 0     0     my ($dbh) = @_;
130 0           $dbh->begin_work();
131 0           foreach my $row ( split qr/;\s*/msx, $params{'init'} ) {
132 0           $dbh->do($row);
133             }
134 0           $dbh->commit;
135 0           return;
136             }
137 0           }
138              
139             # Construct deinit method
140 0           my $deinit;
141 0 0         if ( ref $params{'deinit'} eq 'CODE' ) {
142 0           $deinit = $params{'deinit'};
143             }
144             else { # SCALAR
145             $deinit = sub {
146 0     0     my ($dbh) = @_;
147 0           $dbh->begin_work();
148 0           foreach my $row ( split qr/;\s*/msx, $params{'deinit'} ) {
149 0           $dbh->do($row);
150             }
151 0           $dbh->commit;
152 0           return;
153             }
154 0           }
155              
156             # Construct _cleanup method
157             my $_cleanup = sub {
158 0     0     my ( $dbh, $name ) = @_; ## no critic (Variables::ProhibitReusedNames)
159              
160             # Drop database
161 0           my $_log = Log::Any->get_logger( category => 'Database::Temp' ); ## no critic (Variables::ProhibitReusedNames)
162 0           my $temp_db_name = $name;
163 0           my $temp_user_name = $name;
164 0           my $main = {
165             'dsn' => _compile_dsn( 'postgres', undef, undef, 'Database--Temp--Driver--Pg' ),
166             'username' => undef,
167             'password' => undef,
168             'attr' => {
169             'AutoCommit' => 1,
170             'RaiseError' => 1,
171             'PrintError' => 1,
172             'pg_server_prepare' => 0,
173             'TraceLevel' => 0,
174             },
175             };
176 0           my $main_dbh = DBI->connect( $main->{'dsn'}, $main->{'username'}, $main->{'password'}, $main->{'attr'}, );
177 0           my $rc = $main_dbh->do("ALTER DATABASE \"$temp_db_name\" WITH ALLOW_CONNECTIONS false");
178 0           $rc = $main_dbh->do("SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname='$temp_db_name'");
179 0           $_log->infof( 'Dropping database \'%s\'', $temp_db_name );
180 0           $rc = $main_dbh->do("DROP DATABASE \"$temp_db_name\"");
181              
182 0 0         if ($rc) {
183 0           $_log->debugf( 'Dropped temp database \'%s\'', $temp_db_name );
184             }
185             else {
186 0           $_log->warningf( 'Probably not managed to drop temp database \'%s\'', $temp_db_name );
187             }
188 0           $rc = $main_dbh->do("DROP ROLE \"$temp_user_name\"");
189 0 0         if ($rc) {
190 0           $_log->debugf( 'Dropped temp user \'%s\'', $temp_user_name );
191             }
192             else {
193 0           $_log->warningf( 'Probably not managed to drop temp user \'%s\'', $temp_user_name );
194             }
195 0           $main_dbh->disconnect();
196 0           };
197              
198             # Create database representing object.
199             return Database::Temp::DB->new(
200             driver => __PACKAGE__ =~ m/^Database::Temp::Driver::(.*)$/msx,
201             name => $params{'name'},
202 0   0       cleanup => $params{'cleanup'} // 0,
203             _cleanup => $_cleanup,
204             _start => $_start,
205             init => $init,
206             deinit => $deinit,
207             dsn => $dsn,
208             username => $name,
209             password => $name,
210             attr => \%attrs,
211             info => \%info,
212             );
213             }
214              
215             1;
216              
217             __END__
218              
219             =pod
220              
221             =encoding UTF-8
222              
223             =head1 NAME
224              
225             Database::Temp::Driver::Pg - Create an ad-hoc database, driver for Postgres
226              
227             =head1 VERSION
228              
229             version 0.003
230              
231             =head2 is_available
232              
233             Can this driver provide a database?
234              
235             Return boolean.
236              
237             =head2 new
238              
239             Create a temp database.
240              
241             User should never call this subroutine directly, only via L<Database::Temp>.
242              
243             =head1 AUTHOR
244              
245             Mikko Koivunalho <mikkoi@cpan.org>
246              
247             =head1 COPYRIGHT AND LICENSE
248              
249             This software is copyright (c) 2023 by Mikko Johannes Koivunalho.
250              
251             This is free software; you can redistribute it and/or modify it under
252             the same terms as the Perl 5 programming language system itself.
253              
254             =cut