Code

Updated locales
[gosa.git] / gosa-si / modules / DBsqlite.pm
1 package GOsaSI::DBsqlite;
3 use strict;
4 use warnings;
7 use Time::HiRes qw(usleep);
8 use Data::Dumper;
9 use GOsaSI::GosaSupportDaemon;
11 use Fcntl qw/:DEFAULT :flock/; # import LOCK_* constants
12 use Carp;
13 use DBI;
15 our $col_names = {};
17 sub new {
18         my $class = shift;
19         my $db_name = shift;
21         my $lock = $db_name.".si.lock";
22         my $self = {dbh=>undef,db_name=>undef,db_lock=>undef,db_lock_handle=>undef};
23         my $dbh = DBI->connect("dbi:SQLite:dbname=$db_name", "", "", {RaiseError => 1, AutoCommit => 1, PrintError => 0});
24         
25         $self->{dbh} = $dbh;
26         $self->{db_name} = $db_name;
27         $self->{db_lock} = $lock;
28         bless($self,$class);
30         my $sth = $self->{dbh}->prepare("pragma integrity_check");
31            $sth->execute();
32         my @ret = $sth->fetchall_arrayref();
33            $sth->finish();
34         if(length(@ret)==1 && $ret[0][0][0] eq 'ok') {
35                 &main::daemon_log("0 DEBUG: Database disk image '".$self->{db_name}."' is ok.", 74);
36         } else {
37                 &main::daemon_log("0 ERROR: Database disk image '".$self->{db_name}."' is malformed, creating new database!", 1);
38                 $self->{dbh}->disconnect() or &main::daemon_log("0 ERROR: Could not disconnect from database '".$self->{db_name}."'!", 1);
39                 $self->{dbh}= undef;
40                 unlink($db_name);
41         }
42         return($self);
43 }
46 sub connect {
47         my $self = shift;
48         if(not defined($self) or ref($self) ne 'GOsaSI::DBsqlite') {
49                 &main::daemon_log("0 ERROR: GOsaSI::DBsqlite::connect was called static! Argument was '$self'!", 1);
50                 return;
51         }
52                 
53         $self->{dbh} = DBI->connect("dbi:SQLite:dbname=".$self->{db_name}, "", "", {PrintError => 0, RaiseError => 1, AutoCommit => 1}) or 
54           &main::daemon_log("0 ERROR: Could not connect to database '".$self->{db_name}."'!", 1);
56         return;
57 }
60 sub disconnect {
61         my $self = shift;
62         if(not defined($self) or ref($self) ne 'GOsaSI::DBsqlite') {
63                 &main::daemon_log("0 ERROR: GOsaSI::DBsqlite::disconnect was called static! Argument was '$self'!", 1);
64                 return;
65         }
67         eval {
68                 $self->{dbh}->disconnect();
69         };
70   if($@) {
71                 &main::daemon_log("ERROR: Could not disconnect from database '".$self->{db_name}."'!", 1);
72         }
74         $self->{dbh}= undef;
76         return;
77 }
80 sub lock {
81         my $self = shift;
82         if(not defined($self) or ref($self) ne 'GOsaSI::DBsqlite') {
83                 &main::daemon_log("0 ERROR: GOsaSI::DBsqlite::lock was called static! Argument was '$self'!", 1);
84                 return;
85         }
87         if(not ref $self->{db_lock_handle} or not fileno $self->{db_lock_handle}) {
88                 sysopen($self->{db_lock_handle}, $self->{db_lock}, O_RDWR | O_CREAT, 0600) or &main::daemon_log("0 ERROR: Opening the database ".$self->{db_name}." failed with $!", 1);
89         }
90 get_lock:
91         my $lock_result = flock($self->{db_lock_handle}, LOCK_EX | LOCK_NB);
92         if(not $lock_result) {
93                 &main::daemon_log("0 ERROR: Could not acquire lock for database ".$self->{db_name}, 1);
94                 usleep(250+rand(500));
95                 goto get_lock;
96         } else {
97                 seek($self->{db_lock_handle}, 0, 2);
98                 &main::daemon_log("0 DEBUG: Acquired lock for database ".$self->{db_name}, 74);
99                 $self->connect();
100         }
101         return;
105 sub unlock {
106         my $self = shift;
107         if(not defined($self) or ref($self) ne 'GOsaSI::DBsqlite') {
108                 &main::daemon_log("0 ERROR: GOsaSI::DBsqlite::unlock was called static! Argument was '$self'!", 1);
109                 return;
110         }
111         if(not ref $self->{db_lock_handle}) {
112                 &main::daemon_log("0 BIG ERROR: Lockfile for database ".$self->{db_name}."got closed within critical section!", 1);
113         }
114         flock($self->{db_lock_handle}, LOCK_UN);
115         &main::daemon_log("0 DEBUG: Released lock for database ".$self->{db_name}, 74);
116         $self->disconnect();
117         return;
121 sub create_table {
122         my $self = shift;
123         if(not defined($self) or ref($self) ne 'GOsaSI::DBsqlite') {
124                 &main::daemon_log("0 ERROR: GOsaSI::DBsqlite::create_table was called static! Statement was '$self'!", 1);
125                 return;
126         }
127         my $table_name = shift;
128         my $col_names_ref = shift;
129         my $index_names_ref = shift || undef;
130         my @col_names;
131         my @col_names_creation;
132         foreach my $col_name (@$col_names_ref) {
133                 push(@col_names, $col_name);
134         }
135         $col_names->{ $table_name } = \@col_names;
136         my $col_names_string = join(", ", @col_names);
137         
138         # Not activated yet
139         # Check schema
140         if($self->check_schema($table_name, $col_names_ref)) {
141                 $self->exec_statement("DROP TABLE $table_name");
142                 &main::daemon_log("WARNING: Schema of table $table_name has changed! Table will be recreated!", 3);
143         }
145         my $sql_statement = "CREATE TABLE IF NOT EXISTS $table_name ( $col_names_string )"; 
146         my $res = $self->exec_statement($sql_statement);
147         
148         # Add indices
149         if(defined($index_names_ref) and ref($index_names_ref) eq 'ARRAY') {
150                 foreach my $index_name (@$index_names_ref) {
151                         $self->exec_statement("CREATE ".(($index_name eq 'id')?'UNIQUE':'')." INDEX IF NOT EXISTS $index_name on $table_name ($index_name);");
152                 }
153         }
155         return 0;
159 sub check_schema {
160         my $self = shift;
161         my $table_name = shift;
162         my $col_names_ref = shift;   # ['id INTEGER PRIMARY KEY', 'timestamp VARCHAR(14) DEFAULT \'none\'', ... ]
163         my $col_names_length = @$col_names_ref;
165         my $sql = "PRAGMA table_info($table_name)";
166         my $res = $self->exec_statement($sql);   # [ ['0', 'id', 'INTEGER', '0', undef, '1' ], ['1', 'timestamp', 'VARCHAR(14)', '0', '\'none\'', '0'], ... ]
167         my $db_table_length = @$res;
169         # Tabel does not exists, so no differences
170         if ($db_table_length == 0)
171         {
172                 return 0;
173         }
177         # The number of columns is diffrent
178         if ($col_names_length != $db_table_length) 
179         {
180                 return 1;
181         }
183         # The column name and column type to not match
184         for (my $i=0; $i < $db_table_length; $i++)
185         {
186                 my @col_names_list = split(" ", @$col_names_ref[$i]);
187                 if (($col_names_list[0] ne @{@$res[$i]}[1]) || ($col_names_list[1] ne @{@$res[$i]}[2]))
188                 {
189                         return 1;
190                 }
191         }
194         return 0;
199 sub add_dbentry {
200         my $self = shift;
201         my $arg = shift;
202         my $res = 0;   # default value
204         # if dbh not specified, return errorflag 1
205         my $table = $arg->{table};
206         if( not defined $table ) {
207                 return 1 ;
208         }
210         # if timestamp is not provided, add timestamp   
211         if( not exists $arg->{timestamp} ) {
212                 $arg->{timestamp} = &get_time;
213         }
215         # check primkey and run insert or update
216         my $primkeys = $arg->{'primkey'};
217         my $prim_statement="";
218         if( 0 != @$primkeys ) {   # more than one primkey exist in list
219                 my @prim_list;
220                 foreach my $primkey (@$primkeys) {
221                         if( not exists $arg->{$primkey} ) {
222                                 return (3, "primkey '$primkey' has no value for add_dbentry");
223                         }
224                         push(@prim_list, "$primkey='".$arg->{$primkey}."'");
225                 }
226                 $prim_statement = "WHERE ".join(" AND ", @prim_list);
228                 # check wether primkey is unique in table, otherwise return errorflag
229                 my $sql_statement = "SELECT * FROM $table $prim_statement";
230                 $res = @{ $self->exec_statement($sql_statement) };
231         }
233         # primkey is unique or no primkey specified -> run insert
234         if ($res == 0) {
235                 # fetch column names of table
236                 my $col_names = &get_table_columns($self, $table);
238                 my $create_id=0;
239                 foreach my $col_name (@{$col_names}) {
240                         if($col_name eq "id" && (! exists $arg->{$col_name})) {
241                                 $create_id=1;
242                         }
243                 }
244                 # assign values to column name variables
245                 my @col_list;
246                 my @val_list;
247                 foreach my $col_name (@{$col_names}) {
248                         # use function parameter for column values
249                         if (exists $arg->{$col_name}) {
250                                 push(@col_list, "'".$col_name."'");
251                                 push(@val_list, "'".$arg->{$col_name}."'");
252                         }
253                 }
255                 my $sql_statement;
256                 if($create_id==1) {
257                         $sql_statement = "INSERT INTO $table (id, ".join(", ", @col_list).") VALUES (null, ".join(", ", @val_list).")";
258                 } else {
259                         $sql_statement = "INSERT INTO $table (".join(", ", @col_list).") VALUES (".join(", ", @val_list).")";
260                 }
261                 my $db_res;
262                 my $success=0;
263                 $self->lock();
264                 eval {
265                         my $sth = $self->{dbh}->prepare($sql_statement);
266                         $db_res = $sth->execute();
267                         $sth->finish();
268                         &main::daemon_log("0 DEBUG: Execution of statement '$sql_statement' succeeded!", 74);
269                         $success = 1;
270                 };
271                 if($@) {
272                         eval {
273                                 $self->{dbh}->do("ANALYZE");
274                                 $self->{dbh}->do("VACUUM");
275                         };
276                 }
277                 if($success==0) {
278                         eval {
279                                 my $sth = $self->{dbh}->prepare($sql_statement);
280                                 $db_res = $sth->execute();
281                                 $sth->finish();
282                                 &main::daemon_log("0 DEBUG: Execution of statement '$sql_statement' succeeded!", 74);
283                                 $success = 1;
284                         };
285                         if($@) {
286                                 eval {
287                                         $self->{dbh}->do("ANALYZE");
288                                         $self->{dbh}->do("VACUUM");
289                                 };
290                         }
291                 }
292                 if($success==0) {
293                         eval {
294                                 my $sth = $self->{dbh}->prepare($sql_statement);
295                                 $db_res = $sth->execute();
296                                 $sth->finish();
297                                 &main::daemon_log("0 DEBUG: Execution of statement '$sql_statement' succeeded!", 74);
298                                 $success = 1;
299                         };
300                         if($@) {
301                                 &main::daemon_log("0 ERROR: Execution of statement '$sql_statement' failed with $@", 1);
302                         }
303                 }
304                 $self->unlock();
306                 if( $db_res != 1 ) {
307                         return (4, $sql_statement);
308                 }
310                 # entry already exists -> run update
311         } else  {
312                 my @update_l;
313                 while( my ($pram, $val) = each %{$arg} ) {
314                         if( $pram eq 'table' ) { next; }
315                         if( $pram eq 'primkey' ) { next; }
316                         push(@update_l, "$pram='$val'");
317                 }
318                 my $update_str= join(", ", @update_l);
319                 $update_str= " SET $update_str";
321                 my $sql_statement= "UPDATE $table $update_str $prim_statement";
322                 my $db_res = &update_dbentry($self, $sql_statement );
323         }
325         return 0;
329 sub update_dbentry {
330         my ($self, $sql)= @_;
331         if(not defined($self) or ref($self) ne 'GOsaSI::DBsqlite') {
332                 &main::daemon_log("0 ERROR: GOsaSI::DBsqlite::update_dbentry was called static! Statement was '$self'!", 1);
333                 return;
334         }
335         my $db_answer= $self->exec_statement($sql); 
336         return $db_answer;
340 sub del_dbentry {
341         my ($self, $sql)= @_;;
342         if(not defined($self) or ref($self) ne 'GOsaSI::DBsqlite') {
343                 &main::daemon_log("0 ERROR: GOsaSI::DBsqlite::del_dbentry was called static! Statement was '$self'!", 1);
344                 return;
345         }
346         my $db_res= $self->exec_statement($sql);
347         return $db_res;
351 sub get_table_columns {
352         my $self = shift;
353         if(not defined($self) or ref($self) ne 'GOsaSI::DBsqlite') {
354                 &main::daemon_log("0 ERROR: GOsaSI::DBsqlite::get_table_columns was called static! Statement was '$self'!", 1);
355                 return;
356         }
357         my $table = shift;
358         my @column_names;
360         if(exists $col_names->{$table}) {
361                 foreach my $col_name (@{$col_names->{$table}}) {
362                         push @column_names, ($1) if $col_name =~ /^(.*?)\s.*$/;
363                 }
364         } else {
365                 my @res;
366                 foreach my $column ( @{ $self->exec_statement ( "pragma table_info('$table')" ) } ) {
367                         push(@column_names, @$column[1]);
368                 }
369         }
371         return \@column_names;
375 sub select_dbentry {
376         my ($self, $sql)= @_;
377         if(not defined($self) or ref($self) ne 'GOsaSI::DBsqlite') {
378                 &main::daemon_log("0 ERROR: GOsaSI::DBsqlite::select_dbentry was called static! Statement was '$self'!", 1);
379                 return;
380         }
381         my $error= 0;
382         my $answer= {};
383         my $db_answer= $self->exec_statement($sql); 
384         my @column_list;
386         # fetch column list of db and create a hash with column_name->column_value of the select query
387         $sql =~ /SELECT ([\S\s]*?) FROM ([\S]*?)( |$)/g;
388         my $selected_cols = $1;
389         my $table = $2;
391         # all columns are used for creating answer
392         if ($selected_cols eq '*') {
393                 @column_list = @{ $self->get_table_columns($table) };    
395                 # specific columns are used for creating answer
396         } else {
397                 # remove all blanks and split string to list of column names
398                 $selected_cols =~ s/ //g;          
399                 @column_list = split(/,/, $selected_cols);
400         }
402         # create answer
403         my $hit_counter = 0;
404         my $list_len = @column_list;
405         foreach my $hit ( @{$db_answer} ){
406                 $hit_counter++;
407                 for ( my $i = 0; $i < $list_len; $i++) {
408                         $answer->{ $hit_counter }->{ $column_list[$i] } = @{ $hit }[$i];
409                 }
410         }
412         return $answer;  
416 sub show_table {
417         my $self = shift;
418         if(not defined($self) or ref($self) ne 'GOsaSI::DBsqlite') {
419                 &main::daemon_log("0 ERROR: GOsaSI::DBsqlite::show_table was called static! Statement was '$self'!", 1);
420                 return;
421         }
422         my $table_name = shift;
424         my $sql_statement= "SELECT * FROM $table_name ORDER BY timestamp";
425         my $res= $self->exec_statement($sql_statement);
426         my @answer;
427         foreach my $hit (@{$res}) {
428                 push(@answer, "hit: ".join(', ', @{$hit}));
429         }
431         return join("\n", @answer);
435 sub exec_statement {
436         my $self = shift;
437         my $sql_statement = shift;
438         if(not defined($self) or ref($self) ne 'GOsaSI::DBsqlite') {
439                 &main::daemon_log("0 ERROR: GOsaSI::DBsqlite::exec_statement was called static! Statement was '$self'!", 1);
440                 return;
441         }
443         if(not defined($sql_statement) or length($sql_statement) == 0) {
444                 &main::daemon_log("0 ERROR: GOsaSI::DBsqlite::exec_statement was called with empty statement!", 1);
445                 return;
446         }
448         my @db_answer;
449         my $success= 0;
450         $self->lock();
451         # Give three chances to the sqlite database
452         # 1st chance
453         eval {
454                 my $sth = $self->{dbh}->prepare($sql_statement);
455                 my $res = $sth->execute();
456                 @db_answer = @{$sth->fetchall_arrayref()};
457                 $sth->finish();
458                 $success=1;
459                 &main::daemon_log("0 DEBUG: $sql_statement succeeded.", 74);
460         };
461         if($@) {
462                 eval {
463                         $self->{dbh}->do("ANALYZE");
464                         $self->{dbh}->do("VACUUM");
465                         $self->{dbh}->do("pragma integrity_check");
466                 };
467         }
468         if($success) {
469                 $self->unlock();
470                 return \@db_answer ;
471         }
473         # 2nd chance
474         eval {
475                 usleep(200);
476                 my $sth = $self->{dbh}->prepare($sql_statement);
477                 my $res = $sth->execute();
478                 @db_answer = @{$sth->fetchall_arrayref()};
479                 $sth->finish();
480                 $success=1;
481                 &main::daemon_log("0 DEBUG: $sql_statement succeeded.", 74);
482         };
483         if($@) {
484                 eval {
485                         $self->{dbh}->do("ANALYZE");
486                         $self->{dbh}->do("VACUUM");
487                         $self->{dbh}->do("pragma integrity_check");
488                 };
489         }
490         if($success) {
491                 $self->unlock();
492                 return \@db_answer ;
493         }
495         # 3rd chance
496         eval {
497                 usleep(200);
498                 DBI->trace(6) if($main::verbose >= 7);
499                 my $sth = $self->{dbh}->prepare($sql_statement);
500                 my $res = $sth->execute();
501                 @db_answer = @{$sth->fetchall_arrayref()};
502                 $sth->finish();
503                 DBI->trace(0);
504                 &main::daemon_log("0 DEBUG: $sql_statement succeeded.", 74);
505         };
506         if($@) {
507                 DBI->trace(0);
508                 &main::daemon_log("ERROR: $sql_statement failed with $@", 1);
509         }
510         # TODO : maybe an error handling and an erro feedback to invoking function
511         #my $error = @$self->{dbh}->err;
512         #if ($error) {
513         #       my $error_string = @$self->{dbh}->errstr;
514         #}
516         $self->unlock();
517         return \@db_answer;
521 sub exec_statementlist {
522         my $self = shift;
523         my $sql_list = shift;
524         if(not defined($self) or ref($self) ne 'GOsaSI::DBsqlite') {
525                 &main::daemon_log("0 ERROR: GOsaSI::DBsqlite::exec_statementlist was called static!", 1);
526                 return;
527         }
528         my @db_answer;
530         foreach my $sql_statement (@$sql_list) {
531                 if(defined($sql_statement) && length($sql_statement) > 0) {
532                         push @db_answer, $self->exec_statement($sql_statement);
533                 } else {
534                         next;
535                 }
536         }
538         return \@db_answer;
542 sub count_dbentries {
543         my ($self, $table)= @_;
544         if(not defined($self) or ref($self) ne 'GOsaSI::DBsqlite') {
545                 &main::daemon_log("0 ERROR: GOsaSI::DBsqlite::count_dbentries was called static!", 1);
546                 return;
547         }
548         my $error= 0;
549         my $count= -1;
551         my $sql_statement= "SELECT count() FROM $table";
552         my $db_answer= $self->select_dbentry($sql_statement); 
553         if(defined($db_answer) && defined($db_answer->{1}) && defined($db_answer->{1}->{'count()'})) {
554                 $count = $db_answer->{1}->{'count()'};
555         }
557         return $count;
561 sub move_table {
562         my ($self, $from, $to) = @_;
563         if(not defined($self) or ref($self) ne 'GOsaSI::DBsqlite') {
564                 &main::daemon_log("0 ERROR: GOsaSI::DBsqlite::move_table was called static!", 1);
565                 return;
566         }
568         my $sql_statement_drop = "DROP TABLE IF EXISTS $to";
569         my $sql_statement_alter = "ALTER TABLE $from RENAME TO $to";
570         my $success = 0;
572         $self->lock();
573         eval {
574                 $self->{dbh}->begin_work();
575                 $self->{dbh}->do($sql_statement_drop);
576                 $self->{dbh}->do($sql_statement_alter);
577                 $self->{dbh}->commit();
578                 $success = 1;
579         };
580         if($@) {
581                 $self->{dbh}->rollback();
582                 eval {
583                         $self->{dbh}->do("ANALYZE");
584                 };
585                 if($@) {
586                         &main::daemon_log("ERROR: 'ANALYZE' on database '".$self->{db_name}."' failed with $@", 1);
587                 }
588                 eval {
589                         $self->{dbh}->do("VACUUM");
590                 };
591                 if($@) {
592                         &main::daemon_log("ERROR: 'VACUUM' on database '".$self->{db_name}."' failed with $@", 1);
593                 }
594         }
596         if($success == 0) {
597                 eval {
598                         $self->{dbh}->begin_work();
599                         $self->{dbh}->do($sql_statement_drop);
600                         $self->{dbh}->do($sql_statement_alter);
601                         $self->{dbh}->commit();
602                         $success = 1;
603                 };
604                 if($@) {
605                         $self->{dbh}->rollback();
606                         eval {
607                                 $self->{dbh}->do("ANALYZE");
608                         };
609                         if($@) {
610                                 &main::daemon_log("ERROR: 'ANALYZE' on database '".$self->{db_name}."' failed with $@", 1);
611                         }
612                         eval {
613                                 $self->{dbh}->do("VACUUM");
614                         };
615                         if($@) {
616                                 &main::daemon_log("ERROR: 'VACUUM' on database '".$self->{db_name}."' failed with $@", 1);
617                         }
618                 }
619         }
620         
621         if($success == 0) {
622                 eval {
623                         $self->{dbh}->begin_work();
624                         $self->{dbh}->do($sql_statement_drop);
625                         $self->{dbh}->do($sql_statement_alter);
626                         $self->{dbh}->commit();
627                         $success = 1;
628                 };
629                 if($@) {
630                         $self->{dbh}->rollback();
631                         &main::daemon_log("0 ERROR: GOsaSI::DBsqlite::move_table crashed! Operation failed with $@", 1);
632                 }
633         }
635         &main::daemon_log("0 INFO: GOsaSI::DBsqlite::move_table: Operation successful!", 7);
636         $self->unlock();
638         return;
639
642 1;