File Coverage

blib/lib/CCCP/SQLiteWrap.pm
Criterion Covered Total %
statement 24 230 10.4
branch 0 110 0.0
condition 0 82 0.0
subroutine 8 22 36.3
pod 12 14 85.7
total 44 458 9.6


line stmt bran cond sub pod time code
1             package CCCP::SQLiteWrap;
2              
3 1     1   25967 use 5.010000;
  1         4  
  1         42  
4 1     1   5 use strict;
  1         2  
  1         50  
5              
6             our $VERSION = '0.01';
7              
8 1     1   2184 use DBI;
  1         26358  
  1         78  
9 1     1   3464 use File::Temp;
  1         30693  
  1         83  
10 1     1   905 use File::Copy;
  1         2927  
  1         67  
11 1     1   1003 use Data::UUID;
  1         5492  
  1         120  
12 1     1   13 use Digest::MD5 qw(md5_hex);
  1         3  
  1         82  
13            
14             $CCCP::SQLiteWrap::OnlyPrint = 0;
15              
16 1     1   6 use warnings;
  1         2  
  1         5348  
17            
18             my $t_create_pattern = 'CREATE TABLE IF NOT EXISTS %s (%s)';
19             my $i_create_pattern = 'CREATE INDEX %s ON %s(%s)';
20             my $tr_create_pattern = ['DROP TRIGGER IF EXISTS %s','CREATE TRIGGER IF NOT EXISTS %s %s %s ON %s FOR EACH ROW BEGIN %s; END;'];
21              
22             # one argument - abs path to sqolite base
23             sub connect {
24 0     0 1   my ($class,$path) = @_;
25            
26 0 0         if (ref $class) {
27             # reconnect
28 0           $class->{db} = DBI->connect('dbi:SQLite:dbname='.$class->path, '', '',{RaiseError => 1, InactiveDestroy => 1});
29             } else {
30             # init new handler
31 0           my $obj = bless(
32             {
33             db => DBI->connect('dbi:SQLite:dbname='.$path, '', '',{RaiseError => 1, InactiveDestroy => 1}),
34             path => $path
35             },
36             $class
37             );
38            
39             # check connect error
40 0 0         if ($DBI::errstr) {
41 0           die $DBI::errstr;
42             };
43            
44 0           return $obj;
45             }
46             }
47              
48             sub check {
49 0     0 1   my ($obj) = @_;
50            
51             # check live connection
52 0 0         unless ($obj->db->ping()) {
53 0           return "Can't ping SQLite base from ".$obj->path."\n";
54             };
55            
56             # check database structure
57 0           my $need_rebackup = 0;
58 0           my @table = $obj->show_tables;
59 0           foreach my $table (@table) {
60 0 0         next unless $table;
61 0           eval{$obj->db->selectall_arrayref("SELECT * FROM $table LIMIT 1")};
  0            
62 0 0         if ($DBI::errstr) {
63 0           $need_rebackup++;
64 0           last;
65             };
66             };
67 0 0         if ($need_rebackup) {
68 0           return "SQLite base from ".$obj->path." return error like 'database disk image is malformed' and goto re-dump";
69 0 0         return "Bug in re-dump SQLite" unless $obj->redump();
70             };
71            
72 0           return;
73             }
74              
75 0     0 1   sub db {$_[0]->{'db'}}
76 0     0 1   sub path {$_[0]->{'path'}}
77              
78             # return [{'field1'=>'some_value1',...},{'field1'=>'some_value2',...}]
79             sub select2arhash {
80 0     0 0   my ($obj,$query,@param) = @_;
81 0           my $sth = $obj->db->prepare($query);
82 0           $sth->execute(@param);
83 0           return $sth->fetchall_arrayref({});
84             }
85              
86             sub create_table {
87 0     0 1   my $obj = shift;
88 0 0 0       return unless (@_ or scalar @_ % 2 == 0);
89            
90 0           my $exisis_table = $obj->show_tables;
91 0           my @new_table = ();
92 0           my @create_table = ();
93 0           my $can_fk = $obj->db->selectrow_arrayref('PRAGMA foreign_keys');
94 0           while (my ($name,$param) = splice(@_,0,2)) {
95 0 0 0       next if (not $name or ref $name or not $param or ref $param ne 'HASH' or not exists $param->{fields});
      0        
      0        
      0        
96 0           $name = lc($name);
97 0 0         next if $exisis_table->{$name}++;
98            
99 0           my $desc = ''; my @index = ();
  0            
100 0 0         if (exists $param->{meta}) {
101             # set default value
102 0 0 0       if (exists $param->{meta}->{default} and scalar @{$param->{meta}->{default}} % 2) {
  0            
103 0           while (my ($fild,$defval) = splice(@{$param->{meta}->{default}},0,2)) {
  0            
104 0 0         if (exists $param->{fields}->{$fild}) {
105 0           $param->{fields}->{$fild} .= ' DEFAULT '.$obj->db->quote($defval);
106             };
107             }
108             };
109            
110             # set not null
111 0 0         if (exists $param->{meta}->{not_null}) {
112             map {
113 0 0         if (exists $param->{fields}->{$_}) {
  0            
114 0           $param->{fields}->{$_} .= ' NOT NULL';
115             };
116 0           } @{$param->{meta}->{not_null}};
117             };
118            
119             # set unique
120 0 0         if (exists $param->{meta}->{unique}) {
121             map {
122 0 0         if (exists $param->{fields}->{$_}) {
  0            
123 0           $param->{fields}->{$_} .= ' UNIQUE';
124             };
125 0           } @{$param->{meta}->{unique}};
126             };
127              
128             # set primary key
129 0 0         if (exists $param->{meta}->{pk}) {
130 0           $param->{fields}->{'PRIMARY KEY'} = "(".join(',',map {$obj->db->quote($_)} @{$param->{meta}->{pk}}).")";
  0            
  0            
131             };
132            
133             # set fk
134 0 0 0       if ($can_fk and exists $param->{meta}->{fk}) {
135 0 0         unless ($can_fk->[0]) {
136 0           $obj->db->do('PRAGMA foreign_keys = ON');
137 0           $can_fk->[0] = 1;
138             };
139 0           my @fk = @{$param->{meta}->{fk}};
  0            
140 0 0 0       if (@fk and scalar @fk % 2 == 0 ) {
141 0           while (my ($fk_field,$fk_param) = splice(@fk,0,2)) {
142             # REFERENCES artist(artistid) ON DELETE SET DEFAULT
143 0 0 0       next if (not $fk_field or ref $fk_field or ref $fk_param ne 'HASH' or not exists $param->{fields}->{$fk_field});
      0        
      0        
144 0           $param->{fields}->{$fk_field} .= sprintf(' REFERENCES %s(%s)',$fk_param->{table},$fk_param->{field});
145 0 0         $param->{fields}->{$fk_field} .= ' ON UPDATE '.$fk_param->{on_update} if exists $fk_param->{on_update};
146 0 0         $param->{fields}->{$fk_field} .= ' ON DELETE '.$fk_param->{on_delete} if exists $fk_param->{on_delete};
147             };
148             };
149             };
150            
151             # set index
152 0 0         if (exists $param->{meta}->{index}) {
153 0           my $index = {};
154 0           @index = grep {$_} map {
  0            
155 0           my $ind_md5 = md5_hex(join(',',sort {$a cmp $b} @{$_}));
  0            
  0            
156 0           $index->{$ind_md5}++ ?
157             undef :
158             sprintf(
159             $i_create_pattern,
160             sprintf('_%s',Data::UUID->new()->create_hex()),
161             $name,
162 0 0         join(',',map {$obj->db->quote($_)} @{$_})
  0            
163             );
164 0           } @{$param->{meta}->{index}};
165             };
166            
167             };
168            
169 0           my $create_table = sprintf(
170             $t_create_pattern,
171             $name,
172             join(',',
173 0 0         grep {$_}
    0          
174             map {
175 0           exists $param->{fields}->{$_} ?
176             join(' ',$_ eq 'PRIMARY KEY' ? $_ : $obj->db->quote($_),$param->{fields}->{$_}) :
177             undef
178 0           } ((grep {!/^PRIMARY KEY$/} keys %{$param->{fields}}), 'PRIMARY KEY')
  0            
179             )
180             );
181            
182 0 0         if ($CCCP::SQLiteWrap::OnlyPrint) {
183 0           print join("\n",$create_table,@index);
184 0           print "\n------------------------------\n";
185             } else {
186 0           $obj->do_transaction($create_table,@index)
187             };
188 0           push @new_table,$name;
189             }
190            
191 0 0         return wantarray() ? @new_table : scalar @new_table;
192             }
193              
194             # do over transaction
195             sub do_transaction {
196 0     0 1   my ($obj, @query) = @_;
197 0 0         return unless @query;
198 0 0         $obj->db->begin_work or die $obj->db->errstr;
199 0 0         map {$obj->db->do($_) if $_} @query;
  0            
200 0           $obj->db->commit;
201 0           return;
202             };
203              
204             sub show_tables {
205 0 0   0 0   if (wantarray() ){
206 0           return grep {$_!~/^sqlite_/} map {$_=~s/"//g; $_;} $_[0]->db->tables;
  0            
  0            
  0            
207             } else {
208 0           my $tab_hash = {};
209 0           map {$tab_hash->{$_}++} grep {$_!~/^sqlite_/} map {$_=~s/"//g; $_;} $_[0]->db->tables;
  0            
  0            
  0            
  0            
210 0           return $tab_hash;
211             };
212             }
213              
214             sub close {
215 0     0 1   my ($obj) = @_;
216 0           $obj->db->disconnect;
217             }
218              
219             # re-dump need for
220             # fix error like "database disk image is malformed"
221             # shutdown server (or kill process) while go insert itnto sqlite-base
222             sub redump {
223 0     0 1   my ($obj) = @_;
224 0           $obj->close();
225 0 0 0       if (-e $obj->path and -s _) {
226 0           my $tmp_file = File::Temp->new()->filename;
227 0           my $dump_command = sprintf('sqlite3 %s ".dump" | sqlite3 %s',$obj->path,$tmp_file);
228 0           system($dump_command);
229 0           move($tmp_file,$obj->path);
230             } else {
231 0           unlink $obj->path;
232 0           my $create_command = sprintf('sqlite3 %s "select 1"',$obj->path);
233 0           system($create_command);
234             };
235 0           $obj->connect();
236 0 0         return -e $obj->path ? 1 : 0;
237             }
238              
239             # $obj->create_index('tablename' => [asfd,asfds,sdf], 'safasf' => [asfdsf,asfd])
240             sub create_index {
241 0     0 1   my $obj = shift;
242 0 0 0       return unless (@_ or scalar @_ % 2 == 0);
243            
244 0           my $exisis_table = $obj->show_tables;
245 0           my $ret = 0;
246            
247             # geting param
248 0           my $new_index = {};
249 0           while (my ($t_name,$ind_array) = splice(@_,0,2)) {
250 0 0 0       next if (not $t_name or not exists $exisis_table->{$t_name} or not $ind_array);
      0        
251 0           push @{$new_index->{$t_name}}, $ind_array;
  0            
252             };
253            
254             # check exists index
255 0           foreach my $table (keys %$new_index) {
256 0           my @index = ();
257 0           my $exists_index = {};
258 0           my $index_name = $obj->db->selectall_arrayref('PRAGMA index_list('.$obj->db->quote($table).')');
259 0 0         next unless $index_name;
260 0           map {
261 0           my $i_name = $_->[1];
262 0           my $index_fields = $obj->db->selectrow_arrayref('PRAGMA index_info('.$obj->db->quote($i_name).')');
263 0 0         $exists_index->{md5_hex(join(',',sort {$a cmp $b} @$index_fields))}++ if $index_fields;
  0            
264             } @$index_name;
265             # create new index sql
266 0           foreach my $new_index_fields (@{$new_index->{$table}}) {
  0            
267 0 0 0       next if (not $new_index_fields or ref $new_index_fields ne 'ARRAY');
268 0 0         next if $exists_index->{md5_hex(join(',',sort {$a cmp $b} @$new_index_fields))}++;
  0            
269 0           my $unic_name = sprintf('_%s',Data::UUID->new()->create_hex());
270 0           push @index, sprintf(
271             $i_create_pattern,
272             $unic_name,
273             $table,
274 0           join(',',map {$obj->db->quote($_)} @$new_index_fields)
275             );
276 0           $ret++;
277             };
278            
279             # create new index in base
280 0 0         if ($CCCP::SQLiteWrap::OnlyPrint) {
281 0           print join("\n",@index);
282 0           print "\n------------------------------\n";
283             } else {
284 0           $obj->do_transaction(@index);
285             };
286             };
287            
288 0           return $ret;
289             }
290              
291             # check table on exists (bool)
292             sub table_exists {
293 0     0 1   my ($obj,$table) = @_;
294 0 0         return unless $table;
295            
296 0 0         return scalar(grep {/^\Q$table\E$/i} map {$_=~s/"//g; $_;} $obj->db->tables) ? 1 : 0;
  0            
  0            
  0            
297             }
298              
299             # $obj->index_exists('name' => ['field1','field2']);
300             # return name index if exists for whis fields
301             sub index_exists {
302 0     0 1   my ($obj,$table,$ind_fields) = @_;
303            
304 0 0 0       return unless ($table and $ind_fields and ref $ind_fields eq 'ARRAY');
      0        
305 0 0         return unless $obj->table_exists($table);
306            
307 0           my $index_name = $obj->db->selectall_arrayref('PRAGMA index_list('.$obj->db->quote($table).')');
308 0 0         return unless $index_name;
309 0           my $exists_index = {};
310 0           map {
311 0           my $i_name = $_->[1];
312 0           my $index_fields = $obj->db->selectall_arrayref('PRAGMA index_info('.$obj->db->quote($i_name).')');
313 0 0         if ($index_fields) {
314 0           $index_fields = [map {$_->[2]} @$index_fields];
  0            
315 0           $exists_index->{md5_hex(join(',',sort {$a cmp $b} @$index_fields))} = $i_name;
  0            
316             };
317             } @$index_name;
318            
319 0           my $ind_fields_md5 = md5_hex(join(',',sort {$a cmp $b} @$ind_fields));
  0            
320            
321 0 0         return exists $exists_index->{$ind_fields_md5} ? $exists_index->{$ind_fields_md5} : 0;
322             }
323              
324             sub create_trigger {
325 0     0 1   my $obj = shift;
326 0 0 0       return unless (@_ or scalar @_ % 2 == 0);
327            
328 0           my @transaction_query = ();
329            
330 0           my $exisis_table = $obj->show_tables;
331             # cycle for table
332 0           while (my ($t_name,$param) = splice(@_,0,2)) {
333 0 0 0       next if (not $t_name or not exists $exisis_table->{$t_name} or not $param or ref $param ne 'HASH' or not keys %$param);
      0        
      0        
334             # cycle for event listener
335 0           while (my ($t_event_1,$event_param) = each %$param) {
336 0 0 0       next if (not $t_event_1 or not $event_param or ref $event_param ne 'HASH' or not keys %$event_param);
      0        
      0        
337             # last cycle
338 0           while (my ($t_event_2,$sql) = each %$event_param) {
339 0 0 0       next unless ($t_event_2 and $sql and ref $sql eq 'ARRAY' and scalar @$sql);
      0        
      0        
340 0           $sql = [map {s/;\s*$//s; $_} @$sql];
  0            
  0            
341 0           my $tr_name = join('_',map {lc($_)} ('trigger',$t_name,$t_event_1,$t_event_2,md5_hex(lc(join('',@$sql)))));
  0            
342             # delete trigger
343 0           push @transaction_query,sprintf(
344             $tr_create_pattern->[0],
345             $tr_name
346             );
347             # create trigger
348 0           push @transaction_query,sprintf(
349             $tr_create_pattern->[1],
350             $tr_name,
351             uc($t_event_1),
352             uc($t_event_2),
353             $t_name,
354             join(';',@$sql)
355             );
356             };
357             };
358             };
359            
360             # create transaction in base
361 0 0         if ($CCCP::SQLiteWrap::OnlyPrint) {
362 0           print join("\n",@transaction_query);
363 0           print "\n------------------------------\n";
364             } else {
365 0           $obj->do_transaction(@transaction_query);
366             };
367             }
368              
369              
370             1;
371             __END__