File Coverage

blib/lib/Test/Database/Driver.pm
Criterion Covered Total %
statement 130 147 88.4
branch 40 56 71.4
condition 29 40 72.5
subroutine 36 40 90.0
pod 19 19 100.0
total 254 302 84.1


line stmt bran cond sub pod time code
1             package Test::Database::Driver;
2             $Test::Database::Driver::VERSION = '1.113';
3 7     7   72169 use strict;
  7         14  
  7         241  
4 7     7   37 use warnings;
  7         11  
  7         169  
5 7     7   35 use Carp;
  7         11  
  7         509  
6 7     7   36 use File::Spec;
  7         12  
  7         160  
7 7     7   32 use File::Path;
  7         13  
  7         528  
8 7     7   5882 use version;
  7         15801  
  7         41  
9 7     7   7906 use YAML::Tiny qw( LoadFile DumpFile );
  7         42329  
  7         515  
10 7     7   66 use Cwd;
  7         14  
  7         454  
11              
12 7     7   4666 use Test::Database::Handle;
  7         24  
  7         10954  
13              
14             #
15             # GLOBAL CONFIGURATION
16             #
17              
18             # the location where all drivers-related files will be stored
19             my $KEY = '';
20             my $login = getlogin() || getpwuid($<);
21             $login =~ s/\W+//g;
22             my $root = File::Spec->rel2abs(
23             File::Spec->catdir( File::Spec->tmpdir(), "Test-Database-$login" ) );
24              
25             # generic driver class initialisation
26             sub __init {
27 12     12   25 my ($class) = @_;
28              
29             # create directory if needed
30 12         64 my $dir = $class->base_dir();
31 12 50       512 if ( !-e $dir ) {
    50          
32 0         0 mkpath( [$dir] );
33             }
34             elsif ( !-d $dir ) {
35 0         0 croak "$dir is not a directory. Initializing $class failed";
36             }
37              
38             # load the DBI driver (may die)
39 12         36 DBI->install_driver( $class->name() );
40             }
41              
42             #
43             # METHODS
44             #
45             sub new {
46 17     17 1 5300 my ( $class, %args ) = @_;
47              
48 17 100       70 if ( $class eq __PACKAGE__ ) {
49 13 100       41 if ( exists $args{driver_dsn} ) {
50 1         9 my ( $scheme, $driver, $attr_string, $attr_hash, $driver_dsn )
51             = DBI->parse_dsn( $args{driver_dsn} );
52 1         20 $args{dbd} = $driver;
53             }
54 13 100       238 croak "dbd or driver_dsn parameter required" if !exists $args{dbd};
55             eval "require Test::Database::Driver::$args{dbd}"
56 12 50       694 or do { $@ =~ s/ at .*?\z//s; croak $@; };
  0         0  
  0         0  
57 12         39 $class = "Test::Database::Driver::$args{dbd}";
58 12         79 $class->__init();
59             }
60              
61 16   33     45743 my $self = bless {
62             %args,
63             dbd => $class->name() || $args{dbd},
64             },
65             $class;
66              
67 16         105 $self->_load_mapping();
68              
69             # try to connect before returning the object
70 16 50       93 if ( !$class->is_filebased() ) {
71 0 0       0 eval {
72 0         0 DBI->connect_cached( $self->connection_info(),
73             { PrintError => 0 } );
74             } or return;
75             }
76              
77 16         112 return $self;
78             }
79              
80             sub _mapping_file {
81 17     17   84 return File::Spec->catfile( $_[0]->base_dir(), 'mapping.yml' );
82             }
83              
84             sub available_dbname {
85 7     7 1 39 my ($self) = @_;
86 7         25 my $name = $self->_basename();
87 7         39 my %taken = map { $_ => 1 } $self->databases();
  15         45  
88 7         17 my $n = 0;
89 7         39 $n++ while $taken{"$name$n"};
90 7         32 return "$name$n";
91             }
92              
93             sub _load_mapping {
94 16     16   32 my ($self, $file)= @_;
95 16 50       122 $file = $self->_mapping_file() if ! defined $file;
96              
97             # basic mapping info
98 16         51 $self->{mapping} = {};
99 16 100       415 return if !-e $file;
100              
101             # load mapping from file
102 9         50 my $mapping = LoadFile( $file );
103 9   100     11153 $self->{mapping} = $mapping->{$self->driver_dsn()} || {};
104              
105             # remove stale entries
106 9 50       58 $self->_save_mapping( $file ) if $self->_check_mapping();
107             }
108              
109             sub _save_mapping {
110 1     1   12 my ($self, $file )= @_;
111 1 50       67 $file = $self->_mapping_file() if ! defined $file;
112              
113             # update mapping information
114 1         17 my $mapping = {};
115 1 50       109 $mapping = LoadFile( $file ) if -e $file;
116 1         1108 $mapping->{ $self->driver_dsn() } = $self->{mapping};
117              
118             # save mapping information
119 1         19 DumpFile( "$file.tmp", $mapping );
120 1 50       1024 rename "$file.tmp", $file
121             or croak "Can't rename $file.tmp to $file: $!";
122             }
123              
124             sub _check_mapping {
125 9     9   15 my ($self) = @_;
126 9         21 my $mapping = $self->{mapping};
127 9         58 my %database = map { $_ => undef } $self->databases();
  0         0  
128 9         181 my $updated;
129              
130             # check that all databases in the mapping exist
131 9         41 for my $cwd ( keys %$mapping ) {
132 0 0       0 if ( !exists $database{ $mapping->{$cwd} } ) {
133 0         0 delete $mapping->{$cwd};
134 0         0 $updated++;
135             }
136             }
137 9         44 return $updated;
138             }
139              
140             sub make_dsn {
141 7     7 1 2662 my ($self, @args, @pairs) = @_;
142              
143 7         60 push @pairs, join '=', splice @args, 0, 2 while @args;
144              
145 7         51 my $dsn = $self->driver_dsn();
146 7 100       122 return $dsn
147             . ( $dsn =~ /^dbi:[^:]+:$/ ? '' : ';' )
148             . join( ';', @pairs );
149             }
150              
151             sub make_handle {
152 2     2 1 5 my ($self) = @_;
153 2         3 my $handle;
154              
155             # get the database name from the mapping
156 2         18339 my $dbname = $self->{mapping}{ cwd() };
157              
158             # if the database still exists, return it
159 2 100 66     128 if ( $dbname && grep { $_ eq $dbname } $self->databases() ) {
  1         18  
160 1         20 $handle = Test::Database::Handle->new(
161             dsn => $self->dsn($dbname),
162             username => $self->username(),
163             password => $self->password(),
164             name => $dbname,
165             driver => $self,
166             );
167             }
168              
169             # otherwise create the database and update the mapper
170             else {
171 1         73 $handle = $self->create_database();
172 1         12045 $self->{mapping}{ cwd() } = $handle->{name};
173 1         100 $self->_save_mapping();
174             }
175              
176 2         43 return $handle;
177             }
178              
179             sub version_matches {
180 41     41 1 31825 my ( $self, $request ) = @_;
181              
182             # string tests
183 41         136 my $version_string = $self->version_string();
184             return
185 41 100 100     462 if exists $request->{version}
186             && $version_string ne $request->{version};
187             return
188 37 100 100     179 if exists $request->{regex_version}
189             && $version_string !~ $request->{regex_version};
190              
191             # numeric tests
192 33         80 my $version = $self->version();
193             return
194 33 100 100     386 if exists $request->{min_version}
195             && $version < $request->{min_version};
196             return
197 29 100 100     1186 if exists $request->{max_version}
198             && $version >= $request->{max_version};
199              
200 23         150 return 1;
201             }
202              
203             #
204             # ACCESSORS
205             #
206 125     125 1 6132 sub name { return ( $_[0] =~ /^Test::Database::Driver::([:\w]*)/g )[0]; }
207             *dbd = \&name;
208              
209             sub base_dir {
210 50     50 1 96 my ($self) = @_;
211 50   66     244 my $class = ref $self || $self;
212 50 100       124 return $root if $class eq __PACKAGE__;
213 49         240 my $dir = File::Spec->catdir( $root, $class->name() );
214 49 100       175 return $dir if !ref $self; # class method
215 37   66     620 return $self->{base_dir} ||= $dir; # may be overriden in new()
216             }
217              
218             sub version {
219 7     7   60 no warnings;
  7         24  
  7         6433  
220 38   66 38 1 5629 return $_[0]{version}
221             ||= version->new( $_[0]->_version() =~ /^([0-9._]*[0-9])/ );
222             }
223              
224             sub version_string {
225 41   66 41 1 186 return $_[0]{version_string} ||= $_[0]->_version();
226             }
227              
228 5     5 1 735 sub dbd_version { return "DBD::$_[0]{dbd}"->VERSION; }
229              
230 38   66 38 1 319 sub driver_dsn { return $_[0]{driver_dsn} ||= $_[0]->_driver_dsn() }
231 9     9 1 67 sub username { return $_[0]{username} }
232 9     9 1 99 sub password { return $_[0]{password} }
233              
234             sub connection_info {
235 4     4 1 13 return ( $_[0]->driver_dsn(), $_[0]->username(), $_[0]->password() );
236             }
237              
238             # THESE MUST BE IMPLEMENTED IN THE DERIVED CLASSES
239 0     0 1 0 sub drop_database { die "$_[0] doesn't have a drop_database() method\n" }
240 0     0   0 sub _version { die "$_[0] doesn't have a _version() method\n" }
241              
242             # create_database creates the database and returns a handle
243             sub create_database {
244 1   33 1 1 17 my $class = ref $_[0] || $_[0];
245 1 50       41 goto &_filebased_create_database if $class->is_filebased();
246 0         0 die "$class doesn't have a create_database() method\n";
247             }
248              
249             sub databases {
250 13 50   13 1 304176 goto &_filebased_databases if $_[0]->is_filebased();
251 0         0 die "$_[0] doesn't have a databases() method\n";
252             }
253              
254             # THESE MAY BE OVERRIDDEN IN THE DERIVED CLASSES
255 0     0 1 0 sub is_filebased {0}
256 10     10   41 sub _driver_dsn { join ':', 'dbi', $_[0]->name(), ''; }
257              
258             sub dsn {
259 0     0 1 0 my ( $self, $dbname ) = @_;
260 0         0 return $self->make_dsn( database => $dbname );
261             }
262              
263             #
264             # PRIVATE METHODS
265             #
266             sub _set_key {
267 2   50 2   8 $KEY = $_[1] || '';
268 2 100       193 croak "Invalid format for key '$KEY'" if $KEY !~ /^\w*$/;
269             }
270              
271             sub _basename {
272 23 100   23   671 lc join '_', 'TDD', $_[0]->name(), $login, ( $KEY ? $KEY : (), '' );
273             }
274              
275             # generic implementations for file-based drivers
276             sub _filebased_databases {
277 13     13   32 my ($self) = @_;
278 13         59 my $dir = $self->base_dir();
279 13         24 my $basename = qr/^@{[$self->_basename()]}/;
  13         79  
280              
281 13 50       1251 opendir my $dh, $dir or croak "Can't open directory $dir for reading: $!";
282 13         509 my @databases = grep {/$basename/} File::Spec->no_upwards( readdir($dh) );
  15         122  
283 13         471 closedir $dh;
284              
285 13         99 return @databases;
286             }
287              
288             sub _filebased_create_database {
289 1     1   10 my ( $self ) = @_;
290 1         18 my $dbname = $self->available_dbname();
291              
292 1         10 return Test::Database::Handle->new(
293             dsn => $self->dsn($dbname),
294             name => $dbname,
295             driver => $self,
296             );
297             }
298              
299             'CONNECTION';
300              
301             __END__