File Coverage

blib/lib/Test/DBIC/Pg.pm
Criterion Covered Total %
statement 23 79 29.1
branch 1 18 5.5
condition 1 12 8.3
subroutine 8 14 57.1
pod 5 5 100.0
total 38 128 29.6


line stmt bran cond sub pod time code
1             use Moo;
2 4     4   405446 with 'Test::DBIC::DBDConnector';
  4         35433  
  4         17  
3              
4             our $VERSION = "0.99_03";
5              
6             use DBI;
7 4     4   8826  
  4         45410  
  4         184  
8             use parent 'Test::Builder::Module';
9 4     4   27 our @EXPORT = qw( connect_dbic_pg_ok drop_dbic_pg_ok );
  4         8  
  4         33  
10              
11             $Test::DBIC::Pg::LeaveCreatedDatabases //= 0;
12              
13             # allow for DBI options syntax: dbi:Pg(FetchHashKeyName=>NAME_uc):dbname=blah
14             my $dsn_regex = qr{^ dbi:Pg(?:\(.+?\))?: }x;
15              
16             use Types::Standard qw( Bool Dict HashRef Maybe Str StrMatch );
17 4     4   2657 has '+dbi_connect_info' => (
  4         347626  
  4         50  
18             is => 'ro',
19             type => Dict [
20             dsn => StrMatch [$dsn_regex],
21             username => Types::Standard::Optional [Str],
22             password => Types::Standard::Optional [Str],
23             options => Types::Standard::Optional [HashRef],
24             ],
25             default => sub { { dsn => "dbi:Pg:dbname=_test_dbic_pg_$$" } },
26             );
27             has _pg_tmp_connect_dsn => (
28             is => 'rwp',
29             isa => Maybe [
30             Dict [
31             tmp_dsn => StrMatch [$dsn_regex],
32             dbname => Str,
33             pghost => Maybe [Str],
34             dsn => StrMatch [$dsn_regex],
35             username => Types::Standard::Optional [Str],
36             password => Types::Standard::Optional [Str],
37             options => Types::Standard::Optional [HashRef],
38             ]
39             ],
40             );
41             has _tmp_connection => (
42             is => 'lazy',
43             clearer => 1,
44             );
45             has TMPL_DB => (
46             is => 'ro',
47             default => sub {'template1'},
48             );
49             has _did_create => (
50             is => 'rwp',
51             isa => Bool,
52             default => 0,
53             );
54              
55             # Keep a "singleton" around for the functional interface.
56             my $_tdbc_cache;
57              
58             my $self = shift;
59             return DBI->connect(
60 0     0   0 $self->_pg_tmp_connect_dsn->{tmp_dsn},
61             $self->_pg_tmp_connect_dsn->{username},
62             $self->_pg_tmp_connect_dsn->{password},
63             );
64             }
65 0         0  
66             my $self = shift;
67             if ($self->_did_create && !$Test::DBIC::Pg::LeaveCreatedDatabases) {
68             my $dbh = $self->_tmp_connection;
69 2     2 1 2865 local (
70 2 50 33     32 $dbh->{PrintError}, $dbh->{RaiseError},
71 0           $dbh->{PrintWarn}, $dbh->{RaiseWarn}
72             );
73             $dbh->do(
74             sprintf("DROP DATABASE %s", $self->_pg_tmp_connect_dsn->{dbname})
75 0           );
76             $self->_set__did_create(0);
77             $dbh->disconnect;
78 0           }
79 0           }
80 0            
81             my $class = __PACKAGE__;
82             my %args = $class->validate_positional_parameters(
83             [
84             $class->parameter(schema_class => $class->Required),
85 0     0 1   $class->parameter(dbi_connect_info => $class->Optional),
86 0           $class->parameter(pre_deploy_hook => $class->Optional),
87             $class->parameter(post_connect_hook => $class->Optional),
88             ],
89             \@_
90             );
91             $args{dbi_connect_info} //= { dsn => "dbi:Pg:dbname=_test_dbic_pg_$$" };
92              
93             $_tdbc_cache //= $class->new(%args);
94              
95 0   0       local $Test::Builder::Level = $Test::Builder::Level + 1;
96             my $schema = $_tdbc_cache->connect_dbic_ok();
97 0   0       if (!$schema) {
98             undef($_tdbc_cache);
99 0           }
100 0            
101 0 0         return $schema;
102 0           }
103              
104             if (!defined($_tdbc_cache)) {
105 0           my $msg = "no database DROPPED";
106             return $_tdbc_cache->builder->ok(1, $msg);
107             }
108              
109 0 0   0 1   local $Test::Builder::Level = $Test::Builder::Level + 1;
110 0           my $result = $_tdbc_cache->drop_dbic_ok();
111 0            
112             undef($_tdbc_cache);
113              
114 0           return $result;
115 0           }
116              
117 0           my $self = shift;
118             my $dbname = $self->_pg_tmp_connect_dsn->{dbname};
119 0           my $msg = "$dbname DROPPED";
120              
121             my $dbh = $self->_tmp_connection();
122             local ($dbh->{PrintError}, $dbh->{RaiseError});
123 0     0 1   my $rows = $dbh->do("DROP DATABASE $dbname");
124 0           if (! $rows) {
125 0           $self->builder->diag("DROP $dbname: '@{[$DBI::errstr // q/ok/]}'");
126             }
127 0           $dbh->disconnect();
128 0           $self->_clear_tmp_connection;
129 0           $self->_set__did_create(0);
130 0 0          
131 0   0       return $self->builder->ok(1, $msg);
  0            
132             }
133 0            
134 0           my $self = shift;
135 0           $self->validate_positional_parameters(
136             [
137 0           $self->parameter(
138             dbi_connect_info => $self->Required,
139             { store => \my $pg_connect_info }
140             )
141             ],
142             \@_
143             );
144              
145             my $tmp_dsn_info = $self->_parse_pg_dsn($pg_connect_info->{dsn});
146             $self->_set__pg_tmp_connect_dsn(
147             {
148             %$pg_connect_info,
149             %$tmp_dsn_info,
150             }
151             );
152             my $tmp_dbh = $self->_tmp_connection;
153              
154             return [ @{$pg_connect_info}{qw/ dsn username password options /} ];
155             }
156              
157             my $self = shift;
158              
159             local $ENV{PGHOST} = $self->_pg_tmp_connect_dsn->{pghost} if not $ENV{PGHOST};
160             my $dbh = $self->_tmp_connection;
161              
162             my @known_dbs = $dbh->data_sources();
163             my $wants_deploy = not grep {
164             m{dbname=(.+?)(?=;|$)} && $1 eq $self->_pg_tmp_connect_dsn->{dbname}
165 0     0 1   } @known_dbs;
166              
167 0 0         if ($wants_deploy) {
168 0           my $rows = $dbh->do("CREATE DATABASE ". $self->_pg_tmp_connect_dsn->{dbname});
169             $self->_set__did_create(1);
170 0           }
171             $dbh->disconnect();
172 0           $self->_clear_tmp_connection;
173 0 0          
174             return $wants_deploy;
175 0 0         }
176 0            
177 0           my $self = shift;;
178             $self->validate_positional_parameters(
179 0           [ $self->parameter(dsn => $self->Required, {store => \my $dsn}) ],
180 0           \@_
181             );
182 0           my ($pghost) = $dsn =~ m{(?<=host=)(?<host>[-.\w]+?)(?=;|$)}
183             ? $+{host}
184             : undef;
185              
186 0     0     my $template_db = $self->TMPL_DB;
187 0            
188             my ($db_attr) = $dsn =~ m{(?<db_attr>dbname|database|db)(?==)}
189             ? $+{db_attr}
190             : 'dbname';
191             (my $tmp_dsn = $dsn) =~ s{(?<=(?:$db_attr)=)(?<dbname>\w+?)(?=;|$)}{$template_db};
192 4     4   10385 my $dbname = $+{dbname} // "<unknown>";
  4         1526  
  4         582  
193 0 0          
194             return {
195 0           tmp_dsn => $tmp_dsn,
196             dbname => $dbname,
197             pghost => $pghost,
198             };
199 0 0         }
200 0            
201 0   0       around ValidationTemplates => sub {
202             my $vt = shift;
203             my $class = shift;
204 0            
205             use Types::Standard qw( ArrayRef Dict HashRef Maybe Str StrMatch );
206              
207             my $validation_templates = $class->$vt();
208             return {
209             %$validation_templates,
210             connection_info => { type => ArrayRef },
211             dsn => { type => StrMatch [$dsn_regex] },
212             dbi_connect_info => {
213             type => Maybe [Dict [
214 4     4   28 dsn => StrMatch [$dsn_regex],
  4         9  
  4         21  
215             username => Types::Standard::Optional [Str],
216             password => Types::Standard::Optional [Str],
217             options => Types::Standard::Optional [HashRef],
218             ]],
219             default => sub { { dsn => "dbi:Pg:dbname=_test_dbic_pg_$$" } }
220             },
221             };
222             };
223              
224             use namespace::autoclean 0.16;
225             1;
226              
227             =pod
228              
229             =head1 NAME
230              
231             Test::DBIC::Pg - Connect to and deploy a DBIx::Class::Schema on Postgres
232              
233 4     4   5794 =head1 SYNOPSIS
  4         38036  
  4         27  
234              
235             The preferred way:
236              
237             #! perl -w
238             use Test::More;
239             use Test::DBIC::Pg;
240              
241             my $td = Test::DBIC::Pg->new(schema_class => 'My::Schema');
242             my $schema = $td->connect_dbi_ok();
243             ...
244             $schema->storage->disconnect();
245             $td->drop_dbic_ok();
246             done_testing();
247              
248             The compatible with L<Test::DBIC::SQLite> way:
249              
250             #! perl -w
251             use Test::More;
252             use Test::DBIC::Pg;
253             my $schema = connect_dbic_pg_ok('My::Schema');
254             ...
255             $schema->storage->disconnect();
256             drop_dbic_pg_ok();
257             done_testing();
258              
259             =head1 DESCRIPTION
260              
261             This is an implementation of C<Test::DBIC::Pg> that uses the L<Moo::Role>:
262             L<Test::DBIC::DBDConnector> from the L<Test::DBIC::SQLite> package.
263              
264             It will C<import()> L<warnings> and L<strict> for you.
265              
266             =head2 C<< Test::DBIC::Pg->new >>
267              
268             my $td = Test::DBIC::Pg->new(%parameters);
269             my $schema = $td->connect_dbic_ok();
270             ...
271             $schema->storage->disconnect();
272             $td->drop_dbic_ok();
273              
274             =head3 Parameters
275              
276             Named, list:
277              
278             =over
279              
280             =item B<< C<schema_class> >> => C<$schema_class> (I<Required>)
281              
282             The class name of the L<DBIx::Class::Schema> to use.
283              
284             =item B<< C<dbi_connect_info> >> => C<$pg_connect_info> (I<Optional>,
285             C<< { dsn => "dbi:Pg:dbname=_test_dbic_pg_$$" } >>)
286              
287             This is a HashRef that will be used to connect to the PostgreSQL server:
288              
289             =over 8
290              
291             =item B<< C<dsn> >> => C<dbi:Pg:host=mypg;dbname=_my_test_x>
292              
293             This Data Source Name (dsn) must also contain the C<dbi:Pg:> bit that is needed
294             for L<DBI> to connect to your database/server.
295             We do allow for DBI options syntax: C<< dbi:Pg(FetchHashKeyName=>NAME_uc):dbname=blah >>
296              
297             If your database doesn't exist it will be created. This will need an extra
298             temporary database connection.
299              
300             =item B<< C<username> >> => C<$username>
301              
302             This is the username that will be used to connect to the PostgreSQL server, if
303             omitted L<DBD::Pg> will try to use C<$ENV{PGUSER}>.
304              
305             =item B<< C<password> >> => C<$password>
306              
307             This is the password that will be used to connect to the PostgreSQL server, if
308             omitted L<DBD::Pg> will look at C<~/.pgpass> to see if it can find a suitable
309             password in there. (See also postgres docs for C<$ENV{PGPASSWORD}> en
310             C<$ENV{PGPASSFILE}>).
311              
312             =item B<< C<options> >> => C<$options_hash>
313              
314             This options hashref is also passed to the C<< DBIx::Class::Schema->connect() >>
315             method for extra options. This hash will contain the extra key/value pair C<<
316             skip_version => 1 >> whenever the B<wants_deploy> attribute is true.
317              
318             =back
319              
320             =item B<< C<pre_deploy_hook> >> => C<$pre_deploy_hook> (I<Optional>)
321              
322             A CodeRef to execute I<before> C<< $schema->deploy >> is called.
323              
324             This CodeRef is called with an instantiated C<< $your_schema_class >> object as argument.
325              
326             =item B<< C<post_connect_hook> >> => C<$post_connect_hook> (I<Optional>)
327              
328             A coderef to execute I<after> C<< $schema->deploy >> is called, if at all.
329              
330             This coderef is called with an instantiated C<< $your_schema_class >> object as argument.
331              
332             =item B<< C<TMPL_DB> >> => C<$template_database> (I<Optional>, C<template1>)
333              
334             In order to create and drop your test database a temporary connection needs to
335             be made to the PostgreSQL instance from your dsn, but with a template database
336             (tools like C<createdb> and C<dropdb> also do this in the background).
337             The default database for these type of connections is C<template1> - and this
338             module uses that as well - but your DBA could have configured a different
339             database for this function, therefore we support the setting of C<TMPL_DB>.
340              
341             =back
342              
343             =head2 C<< $td->connect_dbic_ok() >>
344              
345             This method is inherited from L<Test::DBIC::DBDConnoctor>.
346              
347             If the database needs deploying, there will be another temporary database
348             connection to the template database in order to issue the C<CREATE DATABASE
349             $dbname> statement.
350              
351             =head3 Returns
352              
353             An initialised instance of C<$schema_class>.
354              
355             =head2 C<< $td->drop_dbic_ok >>
356              
357             This method implements a C<< dropdb $dbname >>, in order not to litter your
358             server with test databases.
359              
360             During this method there will be another temporary database connection to the
361             template database, in order to issue the C<DROP DATABASE $dbname> statement
362             (that cannot be run from the connection with the test database it self).
363              
364             =head2 C<connect_dbic_pg_ok(@parameters)>
365              
366             Create a PostgreSQL database and deploy a dbic_schema. This function is provided
367             for compatibility with L<Test::DBIC::SQLite>.
368              
369             See L<< Test::DBIC::Pg->new|/Test::DBIC::Pg->new >> for further information,
370             although only these 4 arguments are supported.
371              
372             =head3 Parameters
373              
374             Positional:
375              
376             =over
377              
378             =item 1. C<$schema_class> (Required)
379              
380             =item 2. C<$pg_connect_info> (Optional)
381              
382             =item 3. C<$pre_deploy_hook> (Optional)
383              
384             =item 4. C<$post_connect_hook> (Optional)
385              
386             =back
387              
388             =head2 C<drop_dbic_pg_ok()>
389              
390             This function uses the cached information of the call to C<connect_dbic_pg_ok()>
391             and clears it after the database is dropped, using another temporary connection
392             to the template database.
393              
394             See L<the C<drop_dbic_ok()> method|/"-td-drop_dbic_ok">.
395              
396             =head2 Implementation of C<MyDBD_connection_parameters>
397              
398             As there is no fiddling with the already provided connection paramaters, this
399             method sets up the connection parameter for the temporary connection to the
400             template database in order to create or drop the (temporary) test database.
401              
402             =head2 Implementation of C<MyDBD_check_wants_deploy>
403              
404             In this method the temporary connection to the template database is set up and a
405             list of available database is requested - via C<< $dbh->data_sources() >> - to
406             check if the test database already exists. If it doesn't, the database will be
407             created and a true value is returned, otherwise a false value is returned and no
408             new database is created.
409              
410             =begin devel_cover_pod
411              
412             =head2 DEMOLISH
413              
414             Remove created database files when the object goes out of scope.
415              
416             =end devel_cover_pod
417              
418             =head1 AUTHOR
419              
420             E<copy> MMXXI - Abe Timmerman <abeltje@cpan.org>
421              
422             =head1 LICENSE
423              
424             This program is free software; you can redistribute it and/or modify
425             it under the same terms as Perl itself.
426              
427             This program is distributed in the hope that it will be useful,
428             but WITHOUT ANY WARRANTY; without even the implied warranty of
429             MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
430              
431             =cut