File Coverage

lib/DBIx/Oro/Driver/SQLite.pm
Criterion Covered Total %
statement 185 303 61.0
branch 60 148 40.5
condition 31 89 34.8
subroutine 28 37 75.6
pod 18 18 100.0
total 322 595 54.1


line stmt bran cond sub pod time code
1             package DBIx::Oro::Driver::SQLite;
2 19     19   103 use warnings;
  19         35  
  19         849  
3 19     19   128 use strict;
  19         34  
  19         750  
4 19     19   96 use DBIx::Oro;
  19         391  
  19         106  
5             our @ISA;
6 19     19   678 BEGIN { @ISA = 'DBIx::Oro' };
7              
8 19     19   279 use v5.10.1;
  19         55  
  19         1259  
9              
10             # Todo: use 'truncate' for table deletion
11              
12             # Defaults to 500 for SQLITE_MAX_COMPOUND_SELECT
13             our $MAX_COMP_SELECT;
14              
15             BEGIN {
16 19     19   435 $MAX_COMP_SELECT = 500;
17             };
18              
19 19     19   104 use Carp qw/carp/;
  19         32  
  19         1664  
20              
21             # Find and create database file
22 19     19   116 use File::Path;
  19         54  
  19         1192  
23 19     19   103 use File::Basename;
  19         25  
  19         44614  
24              
25             # Default arguments for snippet function
26             my @arguments =
27             qw/start end ellipsis column token/;
28              
29             my $arguments = qr/^(?:start|end|ellipsis|column|token)$/;
30              
31             my @default = ('', '', '...', -1, -15);
32              
33              
34             # Constructor
35             sub new {
36 21     21 1 53 my $class = shift;
37 21         98 my %param = @_;
38 21   50     160 $param{created} //= 0;
39              
40 21         35 my $autocommit = delete $param{autocommit};
41              
42             # Bless object with hash
43 21         99 my $self = bless \%param, $class;
44              
45             # Store filename
46 21   50     252 my $file = $self->{file} = $param{file} // '';
47              
48             # Temporary or memory file
49 21 100 100     200 if (!$file || $file eq ':memory:') {
    100          
50 19         48 $self->{created} = 1;
51             }
52              
53             # Create path for file - based on ORLite
54             elsif (!-e $file) {
55              
56 1         47 my $dir = File::Basename::dirname($file);
57 1 50       14 unless (-d $dir) {
58 0         0 File::Path::mkpath( $dir, { verbose => 0 } );
59             };
60              
61             # Touch the file
62 1 50       76 if (open(TOUCH,'>' . $file)) {
63 1         2 $self->{created} = 1;
64 1         7 close(TOUCH);
65             };
66             };
67              
68             # Data source name
69 21         92 $self->{dsn} = 'dbi:SQLite:dbname=' . $self->{file};
70              
71             # Attach hash
72 21         81 $self->{attached} = {};
73              
74             # Autocommit
75 21         56 ${$self->{autocommit}} =
  21         74  
76 21         40 ${$self->{_autocounter}} = 0;
77              
78             # Set autocommit
79 21 50       69 $self->autocommit($autocommit) if $autocommit;
80              
81             # Return object
82 21         82 $self;
83             };
84              
85              
86             # Initialize database if newly created
87             sub _init {
88 21     21   56 my $self = shift;
89              
90             # Get callback
91 21 100 50     127 my $cb = delete $self->{init} if $self->{init} &&
      66        
92             (ref $self->{init} || '') eq 'CODE';
93              
94             # Import SQL file
95 21         74 my $import = delete $self->{import};
96 21         56 my $import_cb = delete $self->{import_cb};
97              
98             # Initialize database if newly created
99 21 100 66     99 if ($self->created && ($import || $cb)) {
      66        
100              
101             # Start creation transaction
102 1 50       21 unless (
103             $self->txn(
104             sub {
105              
106             # Import SQL file
107 1 50   1   5 if ($import) {
108 0 0       0 $self->import_sql($import, $import_cb) or return -1;
109             };
110              
111             # Release callback
112 1 50       5 if ($cb) {
113 1         2 local $_ = $self;
114 1         5 return $cb->($self);
115             };
116 0         0 return 1;
117             })
118             ) {
119              
120             # Unlink SQLite database
121 0 0       0 if (index($self->file, ':') != 0) {
122 0         0 unlink $self->file;
123             };
124              
125             # Not successful
126 0         0 $self = undef;
127 0         0 return;
128             };
129             };
130              
131 21         252 return 1;
132             };
133              
134              
135             # Connect to database
136             sub _connect {
137 24     24   42 my $self = shift;
138 24         184 my $dbh = $self->SUPER::_connect( sqlite_unicode => 1 );
139              
140             # Turn foreign keys on as default
141 24 50       304 $dbh->do('PRAGMA foreign_keys = ON') unless $self->{foreign_keys};
142              
143             # Set busy timeout
144 24   50     3881 $dbh->sqlite_busy_timeout( $self->{busy_timeout} || 300 );
145              
146             # Reattach possibly attached databases
147 24         52 while (my ($db_name, $file) = each %{$self->{attached}}) {
  24         168  
148 0         0 $self->prep_and_exec("ATTACH '$file' AS ?", [$db_name]);
149             };
150              
151             # Return database handle
152 24         135 $dbh;
153             };
154              
155              
156             # File of database
157 0   0 0 1 0 sub file { $_[0]->{file} // '' };
158              
159              
160             # Database driver
161 1     1 1 6 sub driver { 'SQLite' };
162              
163              
164             # Database was just created
165             sub created {
166 22     22 1 54 my $self = shift;
167              
168             # Creation state is 0
169 22 100       92 return 0 unless $self->{created};
170              
171             # Check for thread id
172 21 50 33     201 if (defined $self->{tid} && $self->{tid} != threads->tid) {
    50          
173 0         0 return ($self->{created} = 0);
174             }
175              
176             # Check for process id
177             elsif ($self->{pid} != $$) {
178 0         0 return ($self->{created} = 0);
179             };
180              
181             # Return creation state
182 21         226 return 1;
183             };
184              
185              
186             # Explain query plan
187             sub explain {
188 1     1 1 429 my $self = shift;
189              
190             # Prepare and execute explain query plan
191 1         14 my ($rv, $sth) = $self->prep_and_exec(
192             'EXPLAIN QUERY PLAN ' . shift, @_
193             );
194              
195             # Query was not succesfull
196 1 50       4 return unless $rv;
197              
198             # Create string
199 1         2 my $string;
200 1         2 foreach ( @{ $sth->fetchall_arrayref([]) }) {
  1         56  
201 2         55 $string .= sprintf("%3d | %3d | %3d | %-60s\n", @$_);
202             };
203              
204             # Return query plan string
205 1         20 return $string;
206             };
207              
208              
209             # Delete with SQLite feature
210             sub delete {
211 8     8 1 874 my $self = shift;
212 8         17 my $secure;
213              
214             # Check if -secure parameter is set
215 8 100 66     90 if ($_[-1] && ref $_[-1] && ref $_[-1] eq 'HASH') {
      66        
216 5   50     33 $secure = delete $_[-1]->{-secure} || 0;
217             };
218              
219             # Delete
220 8 50       27 unless ($secure) {
221              
222 8         74 my $rv = $self->SUPER::delete(@_);
223              
224             # Decrement autocommit
225 8 100       36 $self->_decr_commit if $rv;
226              
227 8         39 return $rv;
228             }
229              
230             # Delete securely
231             else {
232              
233             # Security value
234 0         0 my $sec_value;
235              
236             # Retrieve secure delete pragma
237 0         0 my ($rv, $sth) = $self->prep_and_exec('PRAGMA secure_delete');
238 0 0       0 $sec_value = $sth->fetchrow_array if $rv;
239 0         0 $sth->finish;
240              
241             # Set secure_delete pragma
242 0 0       0 $self->do('PRAGMA secure_delete = ON') unless $sec_value;
243              
244             # Delete
245 0         0 $rv = $self->SUPER::delete(@_);
246              
247             # Decrement autocommit
248 0 0       0 $self->_decr_commit if $rv;
249              
250             # Reset secure_delete pragma
251 0 0       0 $self->do('PRAGMA secure_delete = OFF') unless $sec_value;
252              
253             # Return value
254 0         0 return $rv;
255             };
256             };
257              
258              
259             # Insert values to database
260             sub insert {
261 856     856 1 23081 my $self = shift;
262              
263             # Get table name
264 856 50       2476 my $table = $self->_table_name(\@_) or return;
265              
266             # No parameters
267 856 50       1600 return unless $_[0];
268              
269             # Properties
270 856 50 66     3860 my $prop = shift if ref $_[0] eq 'HASH' && ref $_[1];
271              
272             # Single insert
273 856 100       1296 if (ref $_[0] eq 'HASH') {
    50          
274              
275             # Param
276 825         766 my %param = %{ shift(@_) };
  825         2769  
277              
278             # Create insert arrays
279 825         870 my (@keys, @values);
280 825         2025 while (my ($key, $value) = each %param) {
281             # Insert pairs
282 1248 50 33     9133 next if !ref $key && $key !~ $DBIx::Oro::KEY_REGEX;
283 1248         1341 push @keys, $key;
284 1248         3180 push @values, $value;
285             };
286              
287             # Nothing to insert
288 825 50       1423 return unless @keys;
289              
290             # Create insert string
291 825         874 my $sql = 'INSERT ';
292              
293 825 50 33     1608 if ($prop && (my $oc = $prop->{-on_conflict})) {
294 0 0       0 if ($oc eq 'replace') {
    0          
295 0         0 $sql = 'REPLACE '
296             }
297             elsif ($oc eq 'ignore') {
298 0         0 $sql .= 'IGNORE '
299             };
300             };
301              
302 1248         4185 $sql .= 'INTO ' . $table .
303 825         1575 ' (' . join(', ', map { "`$_`" } @keys) . ') VALUES (' . DBIx::Oro::_q(\@values) . ')';
304              
305             # Prepare and execute
306 825         2373 my $rv = $self->prep_and_exec( $sql, \@values );
307              
308             # Decrement autocommit
309 825 100       2770 $self->_decr_commit if $rv;
310              
311 825         3793 return $rv;
312             }
313              
314             # Multiple inserts
315             elsif (ref($_[0]) eq 'ARRAY') {
316              
317 31 100       93 return unless $_[1];
318              
319 30         70 my @keys = @{ shift(@_) };
  30         83  
320              
321             # Default values
322 30         50 my @default = ();
323              
324             # Check if keys are defaults
325 30         216 my $i = 0;
326 30         40 my @default_keys;
327 30         127 while ($keys[$i]) {
328              
329             # No default - next
330 83 100       223 $i++, next unless ref $keys[$i];
331              
332             # Has default value
333 7         13 my ($key, $value) = @{ splice( @keys, $i, 1) };
  7         21  
334 7         12 push(@default_keys, $key);
335 7         17 push(@default, $value);
336             };
337              
338             # Unshift default keys to front
339 30         52 unshift(@keys, @default_keys);
340              
341 30         101 my $sql = 'INSERT INTO ' . $table . ' (' . join(', ', map { "`$_`" } @keys) . ') ';
  83         220  
342 30         118 my $union = 'SELECT ' . DBIx::Oro::_q(\@keys);
343              
344             # Maximum bind variables
345 30         136 my $max = ($MAX_COMP_SELECT / @keys) - @keys;
346              
347 30 100       90 if (scalar @_ <= $max) {
348              
349             # Add data unions
350 27         115 $sql .= $union . ((' UNION ' . $union) x ( scalar(@_) - 1 ));
351              
352             # Prepare and execute with prepended defaults
353 137         488 my @rv = $self->prep_and_exec(
354             $sql,
355 27         63 [ map { (@default, @$_); } @_ ]
356             );
357              
358             # Decrement autocommit
359 27 100       174 $self->_decr_commit if $rv[0];
360              
361 27         491 return @rv;
362             }
363              
364             # More than SQLite MAX_COMP_SELECT insertions
365             else {
366              
367 3         7 my ($rv, @v_array);
368 3         424 my @values = @_;
369              
370             # Start transaction
371             $self->txn(
372             sub {
373 3     3   98 while (@v_array = splice(@values, 0, $max - 1)) {
374              
375             # Delete undef values
376 28 50       184 @v_array = grep($_, @v_array) unless @_;
377              
378             # Add data unions
379 28         462 my $sub_sql = $sql . $union .
380             ((' UNION ' . $union) x ( scalar(@v_array) - 1 ));
381              
382             # Prepare and execute
383 6061         7156 my $rv_part = $self->prep_and_exec(
384             $sub_sql,
385 28         75 [ map { (@default, @$_); } @v_array ]
386             );
387              
388             # Rollback transaction
389 28 50       784 return -1 unless $rv_part;
390 28         897 $rv += $rv_part;
391             };
392              
393 3 50       38 }) or return;
394              
395             # Decrement autocommit
396 3 50       52 $self->_decr_commit if $rv;
397              
398             # Everything went fine
399 3         164 return $rv;
400             };
401             };
402              
403             # Unknown query
404 0         0 return;
405             };
406              
407              
408             # Update existing values in the database
409             sub update {
410 22     22 1 718 my $self = shift;
411              
412 22         126 my $rv = $self->SUPER::update(@_);
413              
414             # Decrement autocommit
415 22 100       86 $self->_decr_commit if $rv;
416              
417 22         89 return $rv;
418             };
419              
420              
421             sub merge {
422 7     7 1 15 my $self = shift;
423              
424 7         51 my ($rv, $type) = $self->SUPER::merge(@_);
425              
426 7 50 100     56 if ($rv && $type eq 'insert' && ${$self->{autocommit}}) {
  3   66     15  
427 0         0 ${$self->{_autocounter}}--;
  0         0  
428             };
429              
430 7 50       43 return wantarray ? ($rv, $type) : $rv;
431             };
432              
433             # Attach database
434             sub attach {
435 1     1 1 9 my ($self, $db_name, $file) = @_;
436              
437 1   50     4 $file //= '';
438              
439             # Attach file, memory or temporary database
440 1         19 my $rv = scalar $self->prep_and_exec("ATTACH '$file' AS ?", [$db_name]);
441              
442 1         6 $self->{attached}->{$db_name} = $file;
443              
444 1         4 return $rv;
445             };
446              
447              
448             # Detach database
449             sub detach {
450 0     0 1 0 my $self = shift;
451 0 0       0 return unless $_[0];
452              
453             # Detach all databases
454 0         0 foreach my $db_name (@_) {
455 0         0 delete $self->{attached}->{$db_name};
456 0 0       0 return unless $self->prep_and_exec('DETACH ?', [$db_name]);
457             };
458              
459 0         0 return 1;
460             };
461              
462              
463             # Wrapper for sqlite last_insert_row_id
464             sub last_insert_id {
465 12     12 1 80 shift->dbh->sqlite_last_insert_rowid;
466             };
467              
468              
469             # Create matchinfo function
470             sub matchinfo {
471 0     0 1 0 my $self = shift;
472              
473             # Use no multibyte characters
474 19     19   311 use bytes;
  19         32  
  19         173  
475              
476             # Format string
477 0 0 0     0 my $format = lc(shift) if $_[0] && $_[0] =~ /^[pcnalsx]+$/i;
478              
479             # Return anonymous subroutine
480             return sub {
481 0     0   0 my $column;
482 0 0       0 if (@_) {
483 0   0     0 $column = shift || 'content';
484              
485             # Format string
486 0 0 0     0 $format = lc(shift) if $_[0] && $_[0] =~ /^[pcnalsx]+$/i;
487             };
488              
489             # Sort format for leading 'pc' if needed
490 0 0       0 if ($format) {
491 0         0 for ($format) {
492              
493             # Sort alphabetically
494 0         0 $_ = join('', sort split('', $_));
495              
496             # Delete repeating characters
497 0         0 s/(.)\1+/$1/g;
498              
499             # Prepend 'pc' if necessary
500 0 0       0 if (/[xals]/) {
501 0         0 tr/pc//d; # Delete 'pc'
502 0         0 $_ = 'pc' . $format; # Prepend 'pc'
503             };
504             };
505             }
506              
507             # No format given
508             else {
509 0         0 $format = 'pcx';
510             };
511              
512             # Return anonymous subroutine
513             return sub {
514             return
515 0         0 'matchinfo(' . $column . ', "' . $format . '")',
516             \&_matchinfo_return,
517             $format;
518 0         0 };
519 0         0 };
520             };
521              
522              
523             # Treat matchinfo return
524             sub _matchinfo_return {
525 0     0   0 my ($blob, $format) = @_;
526              
527             # Get 32-bit blob chunks
528 0         0 my @matchinfo = unpack('l' . (length($blob) * 4), $blob);
529              
530             # Parse format character
531 0         0 my %match;
532 0         0 foreach (split '', $format) {
533              
534             # Characters: p, c, n
535 0 0 0     0 if ($_ eq 'p' or $_ eq 'c' or $_ eq 'n') {
    0 0        
    0 0        
      0        
536 0         0 $match{$_} = shift @matchinfo;
537             }
538              
539             # Characters: a, l, s
540             elsif ($_ eq 'a' or $_ eq 'l' or $_ eq 's') {
541 0         0 $match{$_} = [ splice(@matchinfo, 0, $match{c}) ];
542             }
543              
544             # Characters: x
545             elsif ($_ eq 'x') {
546 0         0 my @match;
547 0         0 for (1 .. ($match{p} * $match{c})) {
548 0         0 push(@match, [ splice(@matchinfo, 0, 3) ]);
549             };
550              
551 0         0 $match{$_} = \@match;
552             }
553              
554             # Unknown character
555             else {
556 0         0 shift @matchinfo;
557             };
558             };
559 0         0 return \%match;
560             };
561              
562              
563             # Create offsets function
564             sub offsets {
565 0     0 1 0 my $self = shift;
566 0         0 my $field = shift;
567              
568             # Use no multibyte characters
569 19     19   11138 use bytes;
  19         38  
  19         79  
570              
571             # subroutine
572             return sub {
573 0     0   0 my $column = shift;
574             'offsets(' . ($column // $field // 'content') . ')',
575             sub {
576 0         0 my $blob = shift;
577 0         0 my @offset;
578 0         0 my @array = split(/\s/, $blob);
579 0         0 while (@array) {
580 0         0 push(@offset, [ splice(@array, 0, 4) ]);
581             };
582 0         0 return \@offset;
583             }
584 0   0     0 };
  0   0     0  
585             };
586              
587              
588             # Create snippet function
589             sub snippet {
590 1     1 1 8 my $self = shift;
591              
592             # Snippet parameters
593 1         3 my @snippet;
594              
595             # Parameters are given
596 1 50       4 if ($_[0]) {
597 1         4 my %snippet = ();
598              
599             # Parameters are given as a hash
600 1 50       20 if ($_[0] =~ $arguments) {
601 1         5 %snippet = @_;
602 1         6 foreach (keys %snippet) {
603 3 50       18 carp "Unknown snippet parameter '$_'" unless $_ =~ $arguments;
604             };
605             }
606              
607             # Parameters are given as an array
608             else {
609 0         0 @snippet{@arguments} = @_;
610             };
611              
612             # Trim parameter array and fill gaps with defaults
613 1         4 my ($s, $i) = (0, 0);
614 1         4 foreach (reverse @arguments) {
615 5 100       14 $s = 1 if defined $snippet{$_};
616 5 50 66     26 unshift(@snippet, $snippet{$_} // $default[$i]) if $s;
617 5         8 $i++;
618             };
619             };
620              
621             # Return anonymous subroutine
622 1         3 my $sub = 'sub {
623             my $column = $_[0] ? shift : \'content\';
624             my $str = "snippet(" . $column ';
625              
626 1 50       5 if ($snippet[0]) {
627 1         3 $sub .= ' . ", ' . join(',', map { '\"' . $_ . '\"' } @snippet) . '"';
  5         15  
628             };
629              
630 1         3 $sub .= " . \")\";\n};";
631              
632 1         207 return eval( $sub );
633             };
634              
635              
636             # New table object
637             sub table {
638 26     26 1 11683 my $self = shift;
639              
640             # Get object from superclass
641 26         165 my $table = $self->SUPER::table(@_);
642              
643             # Add autocommit parameters
644 26         73 foreach (qw/autocommit _autocounter
645             foreign_keys/) {
646 78         168 $table->{$_} = $self->{$_};
647             };
648              
649             # Return blessed object
650 26         74 return $table;
651             };
652              
653              
654             # Set foreign_key pragma
655             sub foreign_keys {
656 0     0 1 0 my $self = shift;
657              
658             # Get pragma
659 0 0 0     0 unless (defined $_[0]) {
    0 0        
    0          
660 0         0 return $self->{foreign_keys};
661             }
662              
663             # Turn foreign keys on
664             elsif ($_[0] && !$self->{foreign_keys}) {
665 0         0 $self->dbh->do('PRAGMA foreign_keys = ON');
666 0         0 return ($self->{foreign_keys} = 1);
667             }
668              
669             # Turn foreign keys off
670             elsif (!$_[0] && $self->{foreign_keys}) {
671 0         0 $self->dbh->do('PRAGMA foreign_keys = OFF');
672 0         0 return ($self->{foreign_keys} = 0);
673             };
674              
675 0         0 return;
676             };
677              
678              
679             # Set autocommit
680             sub autocommit {
681 0     0 1 0 my $self = shift;
682              
683             # Get autocommit
684 0 0       0 unless (defined $_[0]) {
685 0   0     0 return ${$self->{autocommit}} || 0;
686             }
687              
688             # Set autocommit
689             else {
690 0         0 my $num = shift;
691 0         0 my $dbh = $self->dbh;
692              
693             # Is a number
694 0 0 0     0 if ($num && $num =~ m/^\d+/o) {
    0          
695 0 0       0 if ($num > 1) {
696 0         0 $dbh->{AutoCommit} = 0;
697 0         0 ${$self->{autocommit}} =
  0         0  
698 0         0 ${$self->{_autocounter}} = $num;
699 0         0 return 1;
700             }
701              
702             else {
703 0         0 $dbh->{AutoCommit} = 1;
704 0         0 ${$self->{_autocounter}} =
  0         0  
705 0         0 ${$self->{autocommit}} = 0;
706 0         0 return 1;
707             };
708             }
709              
710             # Is null
711             elsif (!$num) {
712 0         0 ${$self->{autocommit}} = 0;
  0         0  
713 0 0       0 if (${$self->{_autocounter}}) {
  0         0  
714 0         0 ${$self->{_autocounter}} = 0;
  0         0  
715 0         0 $dbh->commit;
716             };
717 0 0       0 $dbh->{AutoCommit} = 1 unless ${$self->{in_txn}};
  0         0  
718 0         0 return 1;
719             }
720              
721             # Failure
722             else {
723 0         0 return;
724             };
725             };
726             };
727              
728              
729             # Decrement commit counter
730             sub _decr_commit {
731 871     871   1003 my $self = shift;
732              
733             # Autocounter is set
734 871 50       762 if (${$self->{_autocounter}}) {
  871         2521  
735 0           my $auto = --${$self->{_autocounter}};
  0            
736              
737             # Commit is null
738 0 0         unless ($auto) {
739              
740 0 0         $self->dbh->commit unless ${$self->{in_txn}};
  0            
741 0           ${$self->{_autocounter}} = ${$self->{autocommit}};
  0            
  0            
742             };
743             };
744             };
745              
746              
747             1;
748              
749              
750             __END__