Code

Don't print ERROR on DEBUG message.
[gosa.git] / trunk / gosa-si / modules / DBsqlite.pm
index c9ebc440f562bc4d91405895236e40f4125f6e9c..f4e442e41e6a66ffd795b6d6c42a1ac3a8569310 100644 (file)
@@ -7,7 +7,7 @@ use DBI;
 use Data::Dumper;
 use GOSA::GosaSupportDaemon;
 use Time::HiRes qw(usleep);
-use Fcntl ':flock'; # import LOCK_* constants
+use Fcntl qw/:DEFAULT :flock/; # import LOCK_* constants
 
 my $col_names = {};
 
@@ -15,20 +15,21 @@ sub new {
        my $class = shift;
        my $db_name = shift;
 
-       my $lock = $db_name.".si.lock";
+       #my $lock = $db_name.".si.lock";
+       my $lock = $db_name;
        my $self = {dbh=>undef,db_name=>undef,db_lock=>undef,db_lock_handle=>undef};
-       my $dbh = DBI->connect("dbi:SQLite:dbname=$db_name", "", "", {RaiseError => 1, AutoCommit => 1});
+       my $dbh = DBI->connect("dbi:SQLite:dbname=$db_name", "", "", {RaiseError => 1, AutoCommit => 1, PrintError => 0});
        my $sth = $dbh->prepare("pragma integrity_check");
-     $sth->execute();
+       $sth->execute();
        my @ret = $sth->fetchall_arrayref();
+       $sth->finish();
        if(length(@ret)==1 && $ret[0][0][0] eq 'ok') {
                &main::daemon_log("DEBUG: Database image $db_name is ok", 7);
        } else {
                &main::daemon_log("ERROR: Database image $db_name is malformed, creating new database.", 1);
-               $sth->finish();
                $dbh->disconnect();
                unlink($db_name);
-         $dbh = DBI->connect("dbi:SQLite:dbname=$db_name", "", "", {RaiseError => 1, AutoCommit => 1});
+               $dbh = DBI->connect("dbi:SQLite:dbname=$db_name", "", "", {RaiseError => 1, AutoCommit => 1});
        }
        $self->{dbh} = $dbh;
        $self->{db_name} = $db_name;
@@ -40,22 +41,48 @@ sub new {
 
 sub lock {
        my $self = shift;
-       open($self->{db_lock_handle}, ">>".($self->{db_lock})) unless ref $self->{db_lock_handle};
-       flock($self->{db_lock_handle},LOCK_EX);
-       seek($self->{db_lock_handle}, 0, 2);
+       if(not defined($self) or ref($self) ne 'GOSA::DBsqlite') {
+               &main::daemon_log("0 ERROR: GOSA::DBsqlite::lock was called static! Statement was '$self'!", 1);
+               return;
+       }
+       if(not ref $self->{db_lock_handle}) {
+               sysopen($self->{db_lock_handle}, $self->{db_lock}, O_RDWR) or &main::daemon_log("0 ERROR: Opening the database ".$self->{db_name}." failed with $!", 1);
+       }
+       my $lock_result = flock($self->{db_lock_handle}, LOCK_EX);
+       if($lock_result==1) {
+               seek($self->{db_lock_handle}, 0, 2);
+               &main::daemon_log("0 DEBUG: Acquired lock for database ".$self->{db_name}, 7);
+       } else {
+               &main::daemon_log("0 ERROR: Could not acquire lock for database ".$self->{db_name}, 1);
+       }
+       return;
 }
 
 
 sub unlock {
        my $self = shift;
-       flock($self->{db_lock_handle},LOCK_UN);
+       if(not defined($self) or ref($self) ne 'GOSA::DBsqlite') {
+               &main::daemon_log("0 ERROR: GOSA::DBsqlite::unlock was called static! Statement was '$self'!", 1);
+               return;
+       }
+       if(not ref $self->{db_lock_handle}) {
+               &main::daemon_log("0 BIG ERROR: Lockfile for database ".$self->{db_name}."got closed within critical section!", 1);
+       }
+       flock($self->{db_lock_handle}, LOCK_UN);
+       &main::daemon_log("0 DEBUG: Released lock for database ".$self->{db_name}, 7);
+       return;
 }
 
 
 sub create_table {
        my $self = shift;
+       if(not defined($self) or ref($self) ne 'GOSA::DBsqlite') {
+               &main::daemon_log("0 ERROR: GOSA::DBsqlite::create_table was called static! Statement was '$self'!", 1);
+               return;
+       }
        my $table_name = shift;
        my $col_names_ref = shift;
+       my $index_names_ref = shift || undef;
        my @col_names;
        my @col_names_creation;
        foreach my $col_name (@$col_names_ref) {
@@ -70,20 +97,15 @@ sub create_table {
        $col_names->{ $table_name } = \@col_names;
        my $col_names_string = join(", ", @col_names_creation);
        my $sql_statement = "CREATE TABLE IF NOT EXISTS $table_name ( $col_names_string )"; 
-       $self->lock();
-       eval {
-               my $res = $self->{dbh}->do($sql_statement);
-       };
-       if($@) {
-               $self->{dbh}->do("ANALYZE");
-               eval {
-                       my $res = $self->{dbh}->do($sql_statement);
-               };
-               if($@) {
-                       &main::daemon_log("ERROR: $sql_statement failed with $@", 1);
+       my $res = $self->exec_statement($sql_statement);
+       
+       # Add indices
+       if(defined($index_names_ref) and ref($index_names_ref) eq 'ARRAY') {
+               foreach my $index_name (@$index_names_ref) {
+                       $self->exec_statement("CREATE ".(($index_name eq 'id')?'UNIQUE':'')." INDEX IF NOT EXISTS $index_name on $table_name ($index_name);");
                }
        }
-       $self->unlock();
+
 
        return 0;
 }
@@ -96,8 +118,8 @@ sub add_dbentry {
 
        # if dbh not specified, return errorflag 1
        my $table = $arg->{table};
-       if( not defined $table ) { 
-               return 1 ; 
+       if( not defined $table ) {
+               return 1 ;
        }
 
        # if timestamp is not provided, add timestamp   
@@ -120,21 +142,7 @@ sub add_dbentry {
 
                # check wether primkey is unique in table, otherwise return errorflag
                my $sql_statement = "SELECT * FROM $table $prim_statement";
-               $self->lock();
-               eval {
-                       $res = @{ $self->{dbh}->selectall_arrayref($sql_statement) };
-               };
-               if($@) {
-                       $self->{dbh}->do("ANALYZE");
-                       eval {
-                               $res = @{ $self->{dbh}->selectall_arrayref($sql_statement) };
-                       };
-                       if($@) {
-                               &main::daemon_log("ERROR: $sql_statement failed with $@", 1);
-                       }
-               }
-               $self->unlock();
-
+               $res = @{ $self->exec_statement($sql_statement) };
        }
 
        # primkey is unique or no primkey specified -> run insert
@@ -145,11 +153,9 @@ sub add_dbentry {
                my $create_id=0;
                foreach my $col_name (@{$col_names}) {
                        if($col_name eq "id" && (! exists $arg->{$col_name})) {
-                               #&main::daemon_log("0 DEBUG: id field found without value! Creating autoincrement statement!", 7);
                                $create_id=1;
                        }
                }
-
                # assign values to column name variables
                my @col_list;
                my @val_list;
@@ -159,7 +165,7 @@ sub add_dbentry {
                                push(@col_list, "'".$col_name."'");
                                push(@val_list, "'".$arg->{$col_name}."'");
                        }
-               }    
+               }
 
                my $sql_statement;
                if($create_id==1) {
@@ -168,24 +174,53 @@ sub add_dbentry {
                        $sql_statement = "INSERT INTO $table (".join(", ", @col_list).") VALUES (".join(", ", @val_list).")";
                }
                my $db_res;
+               my $success=0;
                $self->lock();
                eval {
-                       $db_res = $self->{dbh}->do($sql_statement);
+                       my $sth = $self->{dbh}->prepare_cached($sql_statement);
+                       $db_res = $sth->execute();
+                       $sth->finish();
+                       &main::daemon_log("0 DEBUG: Execution of statement '$sql_statement' succeeded!", 7);
+                       $success = 1;
                };
                if($@) {
-                       $self->{dbh}->do("ANALYZE");
                        eval {
-                               $db_res = $self->{dbh}->do($sql_statement);
+                               $self->{dbh}->do("ANALYZE");
+                               $self->{dbh}->do("VACUUM");
+                       };
+               }
+               if($success==0) {
+                       eval {
+                               my $sth = $self->{dbh}->prepare_cached($sql_statement);
+                               $db_res = $sth->execute();
+                               $sth->finish();
+                               &main::daemon_log("0 DEBUG: Execution of statement '$sql_statement' succeeded!", 7);
+                               $success = 1;
+                       };
+                       if($@) {
+                               eval {
+                                       $self->{dbh}->do("ANALYZE");
+                                       $self->{dbh}->do("VACUUM");
+                               };
+                       }
+               }
+               if($success==0) {
+                       eval {
+                               my $sth = $self->{dbh}->prepare_cached($sql_statement);
+                               $db_res = $sth->execute();
+                               $sth->finish();
+                               &main::daemon_log("0 DEBUG: Execution of statement '$sql_statement' succeeded!", 7);
+                               $success = 1;
                        };
                        if($@) {
-                               &main::daemon_log("ERROR: $sql_statement failed with $@", 1);
+                               &main::daemon_log("0 ERROR: Execution of statement '$sql_statement' failed with $@", 1);
                        }
                }
                $self->unlock();
 
                if( $db_res != 1 ) {
                        return (4, $sql_statement);
-               } 
+               }
 
                # entry already exists -> run update
        } else  {
@@ -204,24 +239,34 @@ sub add_dbentry {
 
        return 0;
 }
-
-
 sub update_dbentry {
        my ($self, $sql)= @_;
-       my $db_answer= &exec_statement($self, $sql); 
+       if(not defined($self) or ref($self) ne 'GOSA::DBsqlite') {
+               &main::daemon_log("0 ERROR: GOSA::DBsqlite::update_dbentry was called static! Statement was '$self'!", 1);
+               return;
+       }
+       my $db_answer= $self->exec_statement($sql); 
        return $db_answer;
 }
 
 
 sub del_dbentry {
        my ($self, $sql)= @_;;
-       my $db_res= &exec_statement($self, $sql);
+       if(not defined($self) or ref($self) ne 'GOSA::DBsqlite') {
+               &main::daemon_log("0 ERROR: GOSA::DBsqlite::del_dbentry was called static! Statement was '$self'!", 1);
+               return;
+       }
+       my $db_res= $self->exec_statement($sql);
        return $db_res;
 }
 
 
 sub get_table_columns {
        my $self = shift;
+       if(not defined($self) or ref($self) ne 'GOSA::DBsqlite') {
+               &main::daemon_log("0 ERROR: GOSA::DBsqlite::get_table_columns was called static! Statement was '$self'!", 1);
+               return;
+       }
        my $table = shift;
        my @column_names;
 
@@ -229,22 +274,7 @@ sub get_table_columns {
                @column_names = @{$col_names->{$table}};
        } else {
                my @res;
-               $self->lock();
-               eval {
-                       @res = @{$self->{dbh}->selectall_arrayref("pragma table_info('$table')")};
-               };
-               if($@) {
-                       $self->{dbh}->do("ANALYZE");
-                       eval {
-                               @res = @{$self->{dbh}->selectall_arrayref("pragma table_info('$table')")};
-                       };
-                       if($@) {
-                               &main::daemon_log("ERROR: pragma table_info('$table') failed with $@", 1);
-                       }
-               }
-               $self->unlock();
-
-               foreach my $column (@res) {
+               foreach my $column ( $self->exec_statement ( "pragma table_info('$table')" ) ) {
                        push(@column_names, @$column[1]);
                }
        }
@@ -255,9 +285,13 @@ sub get_table_columns {
 
 sub select_dbentry {
        my ($self, $sql)= @_;
+       if(not defined($self) or ref($self) ne 'GOSA::DBsqlite') {
+               &main::daemon_log("0 ERROR: GOSA::DBsqlite::select_dbentry was called static! Statement was '$self'!", 1);
+               return;
+       }
        my $error= 0;
        my $answer= {};
-       my $db_answer= &exec_statement($self, $sql); 
+       my $db_answer= $self->exec_statement($sql); 
        my @column_list;
 
        # fetch column list of db and create a hash with column_name->column_value of the select query
@@ -267,7 +301,7 @@ sub select_dbentry {
 
        # all columns are used for creating answer
        if ($selected_cols eq '*') {
-               @column_list = @{ &get_table_columns($self, $table) };    
+               @column_list = @{ $self->get_table_columns($table) };    
 
                # specific columns are used for creating answer
        } else {
@@ -292,10 +326,14 @@ sub select_dbentry {
 
 sub show_table {
        my $self = shift;
+       if(not defined($self) or ref($self) ne 'GOSA::DBsqlite') {
+               &main::daemon_log("0 ERROR: GOSA::DBsqlite::show_table was called static! Statement was '$self'!", 1);
+               return;
+       }
        my $table_name = shift;
 
        my $sql_statement= "SELECT * FROM $table_name ORDER BY timestamp";
-       my $res= &exec_statement($self, $sql_statement);
+       my $res= $self->exec_statement($sql_statement);
        my @answer;
        foreach my $hit (@{$res}) {
                push(@answer, "hit: ".join(', ', @{$hit}));
@@ -308,28 +346,84 @@ sub show_table {
 sub exec_statement {
        my $self = shift;
        my $sql_statement = shift;
-       my @db_answer;
 
+       if(not defined($self) or ref($self) ne 'GOSA::DBsqlite') {
+               &main::daemon_log("0 ERROR: GOSA::DBsqlite::exec_statement was called static! Statement was '$self'!", 1);
+               return;
+       }
+       if(not defined($sql_statement) or length($sql_statement) == 0) {
+               &main::daemon_log("0 ERROR: GOSA::DBsqlite::exec_statement was called with empty statement!", 1);
+               return;
+       }
+
+       my @db_answer;
+       my $success= 0;
        $self->lock();
+       # Give three chances to the sqlite database
+       # 1st chance
        eval {
-               @db_answer = @{$self->{dbh}->selectall_arrayref($sql_statement)};
+               my $sth = $self->{dbh}->prepare_cached($sql_statement);
+               my $res = $sth->execute();
+               @db_answer = @{$sth->fetchall_arrayref()};
+               $sth->finish();
+               $success=1;
+               &main::daemon_log("0 DEBUG: $sql_statement succeeded.", 7);
        };
        if($@) {
-               $self->{dbh}->do("ANALYZE");
                eval {
-                       @db_answer = @{$self->{dbh}->selectall_arrayref($sql_statement)};
+                       $self->{dbh}->do("ANALYZE");
+                       $self->{dbh}->do("VACUUM");
                };
-               if($@) {
-                       &main::daemon_log("ERROR: $sql_statement failed with $@", 1);
-               }
        }
-       $self->unlock();
+       if($success) {
+               $self->unlock();
+               return \@db_answer ;
+       }
+       
+       # 2nd chance
+       eval {
+               DBI->trace(2) if($main::verbose >= 7);
+               my $sth = $self->{dbh}->prepare_cached($sql_statement);
+               my $res = $sth->execute();
+               @db_answer = @{$sth->fetchall_arrayref()};
+               $sth->finish();
+               DBI->trace(0);
+               $success=1;
+               &main::daemon_log("0 DEBUG: $sql_statement succeeded.", 7);
+       };
+       if($@) {
+               eval {
+                       $self->{dbh}->do("ANALYZE");
+                       $self->{dbh}->do("VACUUM");
+               };
+               DBI->trace(0);
+       }
+       if($success) {
+               $self->unlock();
+               return \@db_answer ;
+       }
+
+       # 3rd chance
+       eval {
+               DBI->trace(2) if($main::verbose >= 7);
+               my $sth = $self->{dbh}->prepare_cached($sql_statement);
+               my $res = $sth->execute();
+               @db_answer = @{$sth->fetchall_arrayref()};
+               $sth->finish();
+               DBI->trace(0);
+               &main::daemon_log("0 DEBUG: $sql_statement succeeded.", 7);
+       };
+       if($@) {
+               DBI->trace(0);
+               &main::daemon_log("ERROR: $sql_statement failed with $@", 1);
+       }
        # TODO : maybe an error handling and an erro feedback to invoking function
        #my $error = @$self->{dbh}->err;
        #if ($error) {
        #       my $error_string = @$self->{dbh}->errstr;
        #}
 
+       $self->unlock();
        return \@db_answer;
 }
 
@@ -337,27 +431,15 @@ sub exec_statement {
 sub exec_statementlist {
        my $self = shift;
        my $sql_list = shift;
+       if(not defined($self) or ref($self) ne 'GOSA::DBsqlite') {
+               &main::daemon_log("0 ERROR: GOSA::DBsqlite::exec_statementlist was called static!", 1);
+               return;
+       }
        my @db_answer;
 
-       foreach my $sql (@$sql_list) {
-               if(defined($sql) && length($sql) > 0) {
-                       # Obtain a new lock for each statement to not block the db for a too long time
-                       $self->lock();
-                       eval {
-                               my @answer = @{$self->{dbh}->selectall_arrayref($sql)};
-                               push @db_answer, @answer;
-                       };
-                       if($@) {
-                               $self->{dbh}->do("ANALYZE");
-                               eval {
-                                       my @answer = @{$self->{dbh}->selectall_arrayref($sql)};
-                                       push @db_answer, @answer;
-                               };
-                               if($@) {
-                                       &main::daemon_log("ERROR: $sql failed with $@", 1);
-                               }
-                       }
-                       $self->unlock();
+       foreach my $sql_statement (@$sql_list) {
+               if(defined($sql_statement) && length($sql_statement) > 0) {
+                       push @db_answer, $self->exec_statement($sql_statement);
                } else {
                        next;
                }
@@ -369,11 +451,15 @@ sub exec_statementlist {
 
 sub count_dbentries {
        my ($self, $table)= @_;
+       if(not defined($self) or ref($self) ne 'GOSA::DBsqlite') {
+               &main::daemon_log("0 ERROR: GOSA::DBsqlite::count_dbentries was called static!", 1);
+               return;
+       }
        my $error= 0;
        my $count= -1;
 
        my $sql_statement= "SELECT count() FROM $table";
-       my $db_answer= &select_dbentry($self, $sql_statement); 
+       my $db_answer= $self->select_dbentry($sql_statement); 
        if(defined($db_answer) && defined($db_answer->{1}) && defined($db_answer->{1}->{'count()'})) {
                $count = $db_answer->{1}->{'count()'};
        }
@@ -384,36 +470,71 @@ sub count_dbentries {
 
 sub move_table {
        my ($self, $from, $to) = @_;
+       if(not defined($self) or ref($self) ne 'GOSA::DBsqlite') {
+               &main::daemon_log("0 ERROR: GOSA::DBsqlite::move_table was called static!", 1);
+               return;
+       }
 
        my $sql_statement_drop = "DROP TABLE IF EXISTS $to";
        my $sql_statement_alter = "ALTER TABLE $from RENAME TO $to";
 
+       &main::daemon_log("INFO move_table called successfully! Exiting!",1 );
+       
        $self->lock();
        eval {
+               $self->{dbh}->begin_work();
                $self->{dbh}->do($sql_statement_drop);
+               $self->{dbh}->do($sql_statement_alter);
+               $self->{dbh}->commit();
        };
        if($@) {
-               $self->{dbh}->do("ANALYZE");
+               $self->{dbh}->rollback();
                eval {
-                       $self->{dbh}->do($sql_statement_drop);
+                       $self->{dbh}->do("ANALYZE");
                };
                if($@) {
-                       &main::daemon_log("ERROR: $sql_statement_drop failed with $@", 1);
+                       &main::daemon_log("ERROR: 'ANALYZE' on database '".$self->{db_name}."' failed with $@", 1);
+               }
+               eval {
+                       $self->{dbh}->do("VACUUM");
+               };
+               if($@) {
+                       &main::daemon_log("ERROR: 'VACUUM' on database '".$self->{db_name}."' failed with $@", 1);
                }
        }
 
        eval {
+               $self->{dbh}->begin_work();
+               $self->{dbh}->do($sql_statement_drop);
                $self->{dbh}->do($sql_statement_alter);
+               $self->{dbh}->commit();
        };
        if($@) {
-               $self->{dbh}->do("ANALYZE");
+               $self->{dbh}->rollback();
+               eval {
+                       $self->{dbh}->do("ANALYZE");
+               };
+               if($@) {
+                       &main::daemon_log("ERROR: 'ANALYZE' on database '".$self->{db_name}."' failed with $@", 1);
+               }
                eval {
-                       $self->{dbh}->do($sql_statement_alter);
+                       $self->{dbh}->do("VACUUM");
                };
                if($@) {
-                       &main::daemon_log("ERROR: $sql_statement_alter failed with $@", 1);
+                       &main::daemon_log("ERROR: 'VACUUM' on database '".$self->{db_name}."' failed with $@", 1);
                }
        }
+       
+       eval {
+               $self->{dbh}->begin_work();
+               $self->{dbh}->do($sql_statement_drop);
+               $self->{dbh}->do($sql_statement_alter);
+               $self->{dbh}->commit();
+       };
+       if($@) {
+               $self->{dbh}->rollback();
+               &main::daemon_log("0 ERROR: GOSA::DBsqlite::move_table crashed! Operation failed with $@", 1);
+       }
        $self->unlock();
 
        return;