File Coverage

blib/lib/App/Sqitch/Engine/sqlite.pm
Criterion Covered Total %
statement 58 86 67.4
branch 2 6 33.3
condition 6 6 100.0
subroutine 25 37 67.5
pod 12 12 100.0
total 103 147 70.0


line stmt bran cond sub pod time code
1              
2             use 5.010;
3 15     15   36848 use strict;
  15         54  
4 15     15   97 use warnings;
  15         49  
  15         363  
5 15     15   92 use utf8;
  15         37  
  15         715  
6 15     15   135 use Try::Tiny;
  15         38  
  15         114  
7 15     15   355 use App::Sqitch::X qw(hurl);
  15         55  
  15         1137  
8 15     15   115 use Locale::TextDomain qw(App-Sqitch);
  15         39  
  15         151  
9 15     15   4547 use App::Sqitch::Plan::Change;
  15         49  
  15         258  
10 15     15   3165 use Path::Class;
  15         37  
  15         527  
11 15     15   79 use Moo;
  15         41  
  15         834  
12 15     15   100 use App::Sqitch::Types qw(URIDB DBH ArrayRef);
  15         38  
  15         145  
13 15     15   6176 use namespace::autoclean;
  15         43  
  15         192  
14 15     15   14178  
  15         34  
  15         343  
15             extends 'App::Sqitch::Engine';
16              
17             our $VERSION = 'v1.3.1'; # VERSION
18              
19             has registry_uri => (
20             is => 'ro',
21             isa => URIDB,
22             lazy => 1,
23             default => sub {
24             my $self = shift;
25             my $uri = $self->uri->clone;
26             my $reg = $self->registry;
27              
28             if ( file($reg)->is_absolute ) {
29             # Just use an absolute path.
30             $uri->dbname($reg);
31             } elsif (my @segs = $uri->path_segments) {
32             # Use the same name, but replace $name.$ext with $reg.$ext.
33             my $bn = file( $segs[-1] )->basename;
34             if ($reg =~ /[.]/ || $bn !~ /[.]/) {
35             $segs[-1] =~ s/\Q$bn\E$/$reg/;
36             } else {
37             my ($b, $e) = split /[.]/, $bn, 2;
38             $segs[-1] =~ s/\Q$b\E[.]$e$/$reg.$e/;
39             }
40             $uri->path_segments(@segs);
41             } else {
42             # No known path, so no name.
43             $uri->dbname(undef);
44             }
45              
46             return $uri;
47             },
48             );
49              
50             my $uri = shift->registry_uri;
51             if ($uri->password) {
52 12     12 1 5775 $uri = $uri->clone;
53 12 100       268 $uri->password(undef);
54 1         58 }
55 1         15 return $uri->as_string;
56             }
57 12         500  
58              
59             has dbh => (
60 6     6 1 6696 is => 'rw',
61 6     6 1 53 isa => DBH,
62 4     4 1 2854 lazy => 1,
63 5     5 1 433 default => sub {
64             my $self = shift;
65             $self->use_driver;
66              
67             my $uri = $self->registry_uri;
68             my $dbh = DBI->connect($uri->dbi_dsn, '', '', {
69             PrintError => 0,
70             RaiseError => 0,
71             AutoCommit => 1,
72             sqlite_unicode => 1,
73             sqlite_use_immediate_transaction => 1,
74             HandleError => sub {
75             my ($err, $dbh) = @_;
76             $@ = $err;
77             @_ = ($dbh->state || 'DEV' => $dbh->errstr);
78             goto &hurl;
79             },
80             Callbacks => {
81             connected => sub {
82             my $dbh = shift;
83             $dbh->do('PRAGMA foreign_keys = ON');
84             return;
85             },
86             },
87             });
88              
89             # Make sure we support this version.
90             my @v = split /[.]/ => $dbh->{sqlite_version};
91             hurl sqlite => __x(
92             'Sqitch requires SQLite 3.7.11 or later; DBD::SQLite was built with {version}',
93             version => $dbh->{sqlite_version}
94             ) unless $v[0] > 3 || ($v[0] == 3 && ($v[1] > 7 || ($v[1] == 7 && $v[2] >= 11)));
95              
96             return $dbh;
97             }
98             );
99              
100             # Need to wait until dbh is defined.
101             with 'App::Sqitch::Role::DBIEngine';
102              
103             has _sqlite3 => (
104             is => 'ro',
105             isa => ArrayRef,
106             lazy => 1,
107             default => sub {
108             my $self = shift;
109              
110             # Make sure we can use this version of SQLite.
111             my @v = split /[.]/ => (
112             split / / => scalar $self->sqitch->capture( $self->client, '-version' )
113             )[0];
114             hurl sqlite => __x(
115             'Sqitch requires SQLite 3.3.9 or later; {client} is {version}',
116             client => $self->client,
117             version => join( '.', @v)
118             ) unless $v[0] > 3 || ($v[0] == 3 && ($v[1] > 3 || ($v[1] == 3 && $v[2] >= 9)));
119              
120             my $dbname = $self->uri->dbname or hurl sqlite => __x(
121             'Database name missing in URI {uri}',
122             uri => $self->uri,
123             );
124              
125             return [
126             $self->client,
127             '-noheader',
128             '-bail',
129             '-batch',
130             '-csv', # or -column or -line?
131             $dbname,
132             ];
133             },
134             );
135              
136              
137              
138             my $self = shift;
139             return $self->dbh->selectcol_arrayref(q{
140             SELECT EXISTS(
141             SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = ?
142 27     27 1 10265 )
  27         583  
143             }, undef, 'changes')->[0];
144 0     0   0 }
145              
146             my $self = shift;
147 0     0 1 0 hurl engine => __x(
148 0         0 'Sqitch database {database} already initialized',
149             database => $self->registry_uri->dbname,
150             ) if $self->initialized;
151              
152             # Load up our database.
153             my @cmd = $self->sqlite3;
154             $cmd[-1] = $self->registry_uri->dbname;
155             my $file = file(__FILE__)->dir->file('sqlite.sql');
156 0     0 1 0 $self->sqitch->run( @cmd, $self->_read($file) );
157 0 0       0 $self->_register_release;
158             }
159              
160             return $DBI::errstr && $DBI::errstr =~ /^\Qno such table:/;
161             }
162              
163 0         0 return $DBI::errstr && $DBI::errstr =~ /^\Qno such column:/;
164 0         0 }
165 0         0  
166 0         0  
167 0         0  
168             q{strftime('%Y-%m-%d %H:%M:%f')};
169             }
170              
171 3   100 3   415 return q{strftime('year:%%Y:month:%%m:day:%%d:hour:%%H:minute:%%M:second:%%S:time_zone:UTC', %s)};
172             }
173              
174             # The order of the concatenated elements is arbitrary.
175 3   100 3   23 # https://www.sqlite.org/lang_aggfunc.html
176             return q{group_concat(%s, ' ')};
177             }
178 0     0   0  
179             my $dt = $_[1];
180 0     0   0 $dt->set_time_zone('UTC');
181             return join ' ', $dt->ymd('-'), $dt->hms(':');
182             }
183 0     0   0  
184             my $self = shift;
185             return $self->sqitch->run( $self->sqlite3, @_ );
186             }
187 1     1   1908  
188             my $self = shift;
189             return $self->sqitch->capture( $self->sqlite3, @_ );
190             }
191              
192             my $self = shift;
193 0     0   0 my $fh = shift;
194             return $self->sqitch->spool( $fh, $self->sqlite3, @_ );
195             }
196              
197 0     0   0 my ($self, $file) = @_;
198 0         0 $self->_run( $self->_read($file) );
199 0         0 }
200              
201             my ($self, $file) = @_;
202             # Suppress STDOUT unless we want extra verbosity.
203 1     1   3159 my $meth = $self->can($self->sqitch->verbosity > 1 ? '_run' : '_capture');
204 1         13 $self->$meth( $self->_read($file) );
205             }
206              
207             my ($self, $fh) = @_;
208 1     1   567 $self->_spool($fh);
209 1         5 }
210              
211             my ($self, $file) = @_;
212             my @cmd = $self->sqlite3;
213 2     2   601 $cmd[-1] = $self->registry_uri->dbname;
214 2         4 return $self->sqitch->run( @cmd, $self->_read($file) );
215 2         13 }
216              
217             my $self = shift;
218             return '.read ' . $self->dbh->quote(shift);
219 0     0 1 0 }
220 0         0  
221             1;
222              
223              
224 0     0 1 0 =head1 Name
225              
226 0 0       0 App::Sqitch::Engine::sqlite - Sqitch SQLite Engine
227 0         0  
228             =head1 Synopsis
229              
230             my $sqlite = App::Sqitch::Engine->load( engine => 'sqlite' );
231 1     1 1 1649  
232 1         4 =head1 Description
233              
234             App::Sqitch::Engine::sqlite provides the SQLite storage engine for Sqitch.
235              
236 0     0 1   =head1 Interface
237 0            
238 0           =head2 Accessors
239 0            
240             =head3 C<client>
241              
242             Returns the path to the SQLite client. If C<--client> was passed to C<sqitch>,
243 0     0     that's what will be returned. Otherwise, it uses the C<engine.sqlite.client>
244 0           configuration value, or else defaults to C<sqlite3> (or C<sqlite3.exe> on
245             Windows), which should work if it's in your path.
246              
247             =head2 Instance Methods
248              
249             =head3 C<sqlite3>
250              
251             Returns a list containing the C<sqlite3> client and options to be passed to it.
252             Used internally when executing scripts.
253              
254             =head1 Author
255              
256             David E. Wheeler <david@justatheory.com>
257              
258             =head1 License
259              
260             Copyright (c) 2012-2022 iovation Inc., David E. Wheeler
261              
262             Permission is hereby granted, free of charge, to any person obtaining a copy
263             of this software and associated documentation files (the "Software"), to deal
264             in the Software without restriction, including without limitation the rights
265             to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
266             copies of the Software, and to permit persons to whom the Software is
267             furnished to do so, subject to the following conditions:
268              
269             The above copyright notice and this permission notice shall be included in all
270             copies or substantial portions of the Software.
271              
272             THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
273             IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
274             FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
275             AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
276             LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
277             OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
278             SOFTWARE.
279              
280             =cut