File Coverage

blib/lib/DBIx/ThinSQL/SQLite.pm
Criterion Covered Total %
statement 82 82 100.0
branch 17 22 77.2
condition 9 17 52.9
subroutine 19 19 100.0
pod 3 3 100.0
total 130 143 90.9


line stmt bran cond sub pod time code
1             package DBIx::ThinSQL::SQLite;
2 1     1   70745 use 5.008005;
  1         3  
3 1     1   4 use strict;
  1         1  
  1         13  
4 1     1   8 use warnings;
  1         5  
  1         24  
5 1     1   3 use Log::Any qw/$log/;
  1         0  
  1         4  
6 1         6 use Exporter::Tidy all =>
7 1     1   146 [qw/create_sqlite_sequence create_functions create_methods/];
  1         1  
8              
9             our $VERSION = '0.0.16';
10              
11             my %sqlite_functions = (
12             debug => sub {
13             my $dbh = shift;
14              
15             $dbh->sqlite_create_function(
16             'debug', -1,
17             sub {
18             if ( @_ && defined $_[0] && $_[0] =~ m/^\s*(select|pragma)/i ) {
19             $dbh->log_debug(@_);
20             }
21             else {
22             $log->debug( join( ' ', map { $_ // 'NULL' } @_ ) );
23             }
24             }
25             );
26             },
27             warn => sub {
28             my $dbh = shift;
29              
30             $dbh->sqlite_create_function(
31             'warn', -1,
32             sub {
33             if ( @_ && defined $_[0] && $_[0] =~ m/^\s*(select|pragma)/i ) {
34             $dbh->log_warn(@_);
35             }
36             else {
37             warn join( ' ', map { $_ // 'NULL' } @_ );
38             }
39             }
40             );
41             },
42             create_sequence => sub {
43             my $dbh = shift;
44             $dbh->sqlite_create_function( 'create_sequence', 1,
45             sub { _create_sequence( $dbh, @_ ) } );
46             },
47             currval => sub {
48             my $dbh = shift;
49             $dbh->sqlite_create_function( 'currval', 1,
50             sub { _currval( $dbh, @_ ) } );
51             },
52             nextval => sub {
53             my $dbh = shift;
54             $dbh->sqlite_create_function( 'nextval', 1,
55             sub { _nextval( $dbh, @_ ) } );
56             },
57             sha1 => sub {
58             require Digest::SHA;
59             my $dbh = shift;
60             $dbh->sqlite_create_function(
61             'sha1', -1,
62             sub {
63             Digest::SHA::sha1(
64             map { utf8::is_utf8($_) ? Encode::encode_utf8($_) : $_ }
65             grep { defined $_ } @_
66             );
67             }
68             );
69             },
70             sha1_hex => sub {
71             require Digest::SHA;
72             my $dbh = shift;
73             $dbh->sqlite_create_function(
74             'sha1_hex',
75             -1,
76             sub {
77             Digest::SHA::sha1_hex(
78             map { utf8::is_utf8($_) ? Encode::encode_utf8($_) : $_ }
79             grep { defined $_ } @_
80             );
81             }
82             );
83             },
84             sha1_base64 => sub {
85             require Digest::SHA;
86             my $dbh = shift;
87             $dbh->sqlite_create_function(
88             'sha1_base64',
89             -1,
90             sub {
91             Digest::SHA::sha1_base64(
92             map { utf8::is_utf8($_) ? Encode::encode_utf8($_) : $_ }
93             grep { defined $_ } @_
94             );
95             }
96             );
97             },
98             agg_sha1 => sub {
99             require Digest::SHA;
100             my $dbh = shift;
101             $dbh->sqlite_create_aggregate( 'agg_sha1', 2,
102             'DBIx::ThinSQL::SQLite::agg_sha1' );
103             },
104             agg_sha1_hex => sub {
105             require Digest::SHA;
106             my $dbh = shift;
107             $dbh->sqlite_create_aggregate( 'agg_sha1_hex', 2,
108             'DBIx::ThinSQL::SQLite::agg_sha1_hex' );
109             },
110             agg_sha1_base64 => sub {
111             require Digest::SHA;
112             my $dbh = shift;
113             $dbh->sqlite_create_aggregate( 'agg_sha1_base64', 2,
114             'DBIx::ThinSQL::SQLite::agg_sha1_base64' );
115             },
116             );
117              
118 7     7   54 sub _croak { require Carp; goto &Carp::croak }
  7         983  
119              
120             sub create_sqlite_sequence {
121 1     1 1 4 my $dbh = shift;
122              
123 1         19 local $dbh->{RaiseError} = 1;
124 1         25 local $dbh->{PrintError} = 0;
125              
126 1         14 my $temp = '_temp' . join( '', map { int( rand($_) ) } 1000 .. 1005 );
  6         16  
127 1         11 $dbh->do("CREATE TABLE $temp(x integer primary key autoincrement)");
128 1         4945 $dbh->do("DROP TABLE $temp");
129              
130 1         3322 return;
131             }
132              
133             sub _create_sequence {
134 3     3   13 my $dbh = shift;
135 3   33     14 my $name = shift || _croak('usage: create_sequence($name)');
136              
137 3         27 local $dbh->{RaiseError} = 1;
138 3         50 local $dbh->{PrintError} = 0;
139              
140             # the sqlite_sequence table doesn't have any constraints so it
141             # would be possible to insert the same sequence twice. Check if
142             # one already exists
143 3         52 my ($val) = (
144             $dbh->selectrow_array(
145             'SELECT seq FROM sqlite_sequence WHERE name = ?',
146             undef, $name
147             )
148             );
149 3 50       429 $val && _croak("create_sequence: sequence already exists: $name");
150 3         25 $log->debug("INSERT INTO sqlite_sequence VALUES('$name',0)");
151 3         92 $dbh->do( 'INSERT INTO sqlite_sequence(name,seq) VALUES(?,?+0)',
152             undef, $name, 0 );
153             }
154              
155             sub _currval {
156 8     8   4887 my $dbh = shift;
157 8   50     23 my $name = shift || die 'usage: currval($name)';
158              
159 8         52 local $dbh->{RaiseError} = 1;
160 8         123 local $dbh->{PrintError} = 0;
161              
162 8         85 my ($val) = (
163             $dbh->selectrow_array(
164             'SELECT seq FROM sqlite_sequence WHERE name = ?',
165             undef, $name
166             )
167             );
168              
169 8 100       860 if ( defined $val ) {
170 7         39 $log->debug( "currval('$name') -> " . $val );
171 7         216 return $val;
172             }
173              
174 1         6 _croak("currval: unknown sequence: $name");
175             }
176              
177             sub _nextval {
178 5     5   1403 my $dbh = shift;
179 5   50     16 my $name = shift || die 'usage: nextval($name)';
180              
181 5         49 local $dbh->{RaiseError} = 1;
182 5         88 local $dbh->{PrintError} = 0;
183 5         42 my $val;
184              
185 5         6 my $i = 0;
186 5         6 while (1) {
187 5 50       18 _croak 'could not obtain nextval' if $i++ > 10;
188              
189 5         49 my ($current) = (
190             $dbh->selectrow_array(
191             'SELECT seq FROM sqlite_sequence WHERE name = ?', undef,
192             $name
193             )
194             );
195 5 100       564 _croak("nextval: unknown sequence: $name") unless defined $current;
196              
197             next
198 3 50       19 unless $dbh->do(
199             'UPDATE sqlite_sequence SET seq = ?+0 '
200             . 'WHERE name = ? AND seq = ?+0',
201             undef, $current + 1, $name, $current
202             );
203              
204 3         15555 $log->debug( "nextval('$name') -> " . ( $current + 1 ) );
205              
206 3         120 return $current + 1;
207             }
208             }
209              
210             sub create_functions {
211 8 100   8 1 64995 _croak('usage: create_functions($dbh,@functions)') unless @_ >= 2;
212              
213 7         11 my $dbh = shift;
214             _croak('handle has no sqlite_create_function!')
215 7 100       9 unless eval { $dbh->can('sqlite_create_function') };
  7         62  
216              
217 6         16 foreach my $name (@_) {
218 12         21 my $subref = $sqlite_functions{$name};
219 12 100       21 _croak( 'unknown function: ' . $name ) unless $subref;
220 11         23 $subref->($dbh);
221             }
222             }
223              
224             my %thinsql_methods = (
225             create_sqlite_sequence => \&_create_sqlite_sequence,
226             create_sequence => \&_create_sequence,
227             currval => \&_currval,
228             nextval => \&_nextval,
229             );
230              
231             sub create_methods {
232 2 50   2 1 7141 _croak('usage: create_methods(@methods)') unless @_ >= 1;
233              
234 2         4 foreach my $name (@_) {
235 4         8 my $subref = $thinsql_methods{$name};
236 4 100       13 _croak( 'unknown method: ' . $name ) unless $subref;
237              
238 1     1   1035 no strict 'refs';
  1         1  
  1         246  
239 3         2 *{ 'DBIx::ThinSQL::db::' . $name } = $subref;
  3         18  
240             }
241             }
242              
243             package DBIx::ThinSQL::SQLite::agg_sha1;
244              
245             sub new {
246 11     11   59725 my $class = shift;
247 11         71 return bless [], $class;
248             }
249              
250             sub step {
251 29     29   24 my $self = shift;
252 29 50 100     277 push( @$self,
      0        
      0        
      100        
      100        
253             utf8::is_utf8( $_[0] // '' )
254             ? [ Encode::encode_utf8( $_[0] // '' ), $_[1] // '' ]
255             : [ $_[0] // '', $_[1] // '' ] );
256             }
257              
258             sub _sort {
259 11     11   12 return map { $_->[0] } sort { $a->[1] cmp $b->[1] } @{ $_[0] };
  29         285  
  23         88  
  11         47  
260             }
261              
262             sub finalize {
263 7     7   17 return Digest::SHA::sha1( $_[0]->_sort );
264             }
265              
266             package DBIx::ThinSQL::SQLite::agg_sha1_hex;
267             our @ISA = ('DBIx::ThinSQL::SQLite::agg_sha1');
268              
269             sub finalize {
270 2     2   9 return Digest::SHA::sha1_hex( $_[0]->_sort );
271             }
272              
273             package DBIx::ThinSQL::SQLite::agg_sha1_base64;
274             our @ISA = ('DBIx::ThinSQL::SQLite::agg_sha1');
275              
276             sub finalize {
277 2     2   10 return Digest::SHA::sha1_base64( $_[0]->_sort );
278             }
279              
280             1;
281             __END__