Description: Fix SQLite DB schema upgrade
Author: Emmanuel Bouthenot <kolter@debian.org>
Bug-Debian: http://bugs.debian.org/642464
Last-Update: 2013-06-15
--- a/src/lib/Upgrade.pm
+++ b/src/lib/Upgrade.pm
@@ -1149,7 +1149,27 @@
 	    $t =~ s/^"main"\.//; # needed for SQLite 3
 	    $t =~ s/^.*\"([^"]+)\"$/$1/;
  	}
-	
+
+    ## Check required tables
+    foreach my $t1 (keys %{$db_struct{'SQLite'}}) {
+        my $found;
+        foreach my $t2 (@tables) {
+        $found = 1 if ($t1 eq $t2);
+        }
+        unless ($found) {
+        unless ($dbh->do("CREATE TABLE $t1 (temporary integer)")) {
+            &do_log('err', 'Could not create table %s in database %s : %s', $t1, $Conf::Conf{'db_name'}, $dbh->errstr);
+            next;
+        }
+        
+        push @report, sprintf('Table %s created in database %s', $t1, $Conf::Conf{'db_name'});
+        &do_log('notice', 'Table %s created in database %s', $t1, $Conf::Conf{'db_name'});
+        push @tables, $t1;
+        $real_struct{$t1} = {};
+        }
+    }
+
+    ## Get fields
 	foreach my $t (@tables) {
 	    next unless (defined $db_struct{$Conf::Conf{'db_type'}}{$t});
 	    
@@ -1270,6 +1290,7 @@
 		    &do_log('info', 'Field %s added to table %s', $f, $t);
 		    $added_fields{$f} = 1;
 		    
+            if ($Conf::Conf{'db_type'} eq 'mysql') {
 		    ## Remove temporary DB field
 		    if ($real_struct{$t}{'temporary'}) {
 			unless ($dbh->do("ALTER TABLE $t DROP temporary")) {
@@ -1277,43 +1298,93 @@
 			}
 			delete $real_struct{$t}{'temporary'};
 		    }
+            }
 		    
 		    next;
 		}
 		
 		## Change DB types if different and if update_db_types enabled
-		if ($Conf::Conf{'update_db_field_types'} eq 'auto' && $Conf::Conf{'db_type'} ne 'SQLite') {
-		    unless (&check_db_field_type(effective_format => $real_struct{$t}{$f},
-						 required_format => $db_struct{$Conf::Conf{'db_type'}}{$t}{$f})) {
-			push @report, sprintf('Field \'%s\'  (table \'%s\' ; database \'%s\') does NOT have awaited type (%s). Attempting to change it...', 
-					      $f, $t, $Conf::Conf{'db_name'}, $db_struct{$Conf::Conf{'db_type'}}{$t}{$f});
-			&do_log('notice', 'Field \'%s\'  (table \'%s\' ; database \'%s\') does NOT have awaited type (%s). Attempting to change it...', 
-				$f, $t, $Conf::Conf{'db_name'}, $db_struct{$Conf::Conf{'db_type'}}{$t}{$f});
-			
-			my $options;
-			if ($not_null{$f}) {
-			    $options .= 'NOT NULL';
+		if ($Conf::Conf{'update_db_field_types'} eq 'auto') {
+			if (grep $_ eq $Conf::Conf{'db_type'}, qw(SQLite mysql)) {
+				unless (&check_db_field_type(effective_format => $real_struct{$t}{$f},
+							required_format => $db_struct{$Conf::Conf{'db_type'}}{$t}{$f})) {
+					push @report, sprintf('Field \'%s\'  (table \'%s\' ; database \'%s\') does NOT have awaited type (%s). Attempting to change it...', 
+							$f, $t, $Conf::Conf{'db_name'}, $db_struct{$Conf::Conf{'db_type'}}{$t}{$f});
+					&do_log('notice', 'Field \'%s\'  (table \'%s\' ; database \'%s\') does NOT have awaited type (%s). Attempting to change it...', 
+							$f, $t, $Conf::Conf{'db_name'}, $db_struct{$Conf::Conf{'db_type'}}{$t}{$f});
+				
+					my $options;
+					if ($not_null{$f}) {
+						$options .= 'NOT NULL';
+					}
+				
+					if ($Conf::Conf{'db_type'} eq 'mysql') {
+						push @report, sprintf("ALTER TABLE $t CHANGE $f $f $db_struct{$Conf::Conf{'db_type'}}{$t}{$f} $options");
+						&do_log('notice', "ALTER TABLE $t CHANGE $f $f $db_struct{$Conf::Conf{'db_type'}}{$t}{$f} $options");
+						unless ($dbh->do("ALTER TABLE $t CHANGE $f $f $db_struct{$Conf::Conf{'db_type'}}{$t}{$f} $options")) {
+							&do_log('err', 'Could not change field \'%s\' in table\'%s\'.', $f, $t);
+							&do_log('err', 'Sympa\'s database structure may have change since last update ; please check RELEASE_NOTES');
+							return undef;
+						}
+					
+					}
+					if ($Conf::Conf{'db_type'} eq 'SQLite') {
+						my @oldfields = ();
+						foreach my $oldfield (keys %{$real_struct{$t}}) {
+							if (defined $db_struct{$Conf::Conf{'db_type'}}{${t}}{$oldfield}) {
+								push(@oldfields, $oldfield);
+							}
+						}
+						my $sqloldfields = join(", ", @oldfields);
+						my $sqlnewfieldstype = join(", ", map { "$_ $db_struct{$Conf::Conf{'db_type'}}{${t}}{$_}" } keys $db_struct{$Conf::Conf{'db_type'}}{${t}});
+
+						# SQLite doesn't support changes on columns types hence we use a dirty workaround here:
+						# we create a temporary table (a clone of the table containing the field to update)
+						# we dump all the data into it
+						# we create a new table with the right schema
+						# we copy all the data from the temporary table into the new one
+						# we delete the temporay table
+						# see: http://www.sqlite.org/faq.html#q11
+						&do_log('notice', 'Using a dirty trick to change SQLite data type see http://www.sqlite.org/faq.html#q11');
+						my $sqlcode = "DROP TABLE IF EXISTS ${t}_backup;";
+						push @report, $sqlcode;
+						&do_log('notice', $sqlcode);
+						$dbh->do($sqlcode);
+						$sqlcode = "CREATE TEMPORARY TABLE ${t}_backup(${sqloldfields});";
+						push @report, $sqlcode;
+						&do_log('notice', $sqlcode);
+						$dbh->do($sqlcode);
+						$sqlcode = "INSERT INTO ${t}_backup SELECT ${sqloldfields} FROM ${t};";
+						push @report, $sqlcode;
+						&do_log('notice', $sqlcode);
+						$dbh->do($sqlcode);
+						$sqlcode = "DROP TABLE ${t};";
+						push @report, $sqlcode;
+						&do_log('notice', $sqlcode);
+						$dbh->do($sqlcode);
+						$sqlcode = "CREATE TABLE ${t}(${sqlnewfieldstype});";
+						push @report, $sqlcode;
+						&do_log('notice', $sqlcode);
+						$dbh->do($sqlcode);
+						$sqlcode = "INSERT INTO ${t} (${sqloldfields}) SELECT ${sqloldfields} FROM ${t}_backup;";
+						push @report, $sqlcode;
+						&do_log('notice', $sqlcode);
+						my $toto = $dbh->do($sqlcode);
+						$sqlcode = "DROP TABLE ${t}_backup;";
+						push @report, $sqlcode;
+						&do_log('notice', $sqlcode);
+						$dbh->do($sqlcode);
+						# Update real structure
+						foreach my $newfield (sort keys $db_struct{$Conf::Conf{'db_type'}}{${t}}) {
+							$real_struct{$t}{$newfield} = $db_struct{$Conf::Conf{'db_type'}}{${t}}{$newfield};
+						}
+					}
+					push @report, sprintf('Field %s in table %s, structure updated', $f, $t);
+					&do_log('info', 'Field %s in table %s, structure updated', $f, $t);
+				}
 			}
-			
-			push @report, sprintf("ALTER TABLE $t CHANGE $f $f $db_struct{$Conf::Conf{'db_type'}}{$t}{$f} $options");
-			&do_log('notice', "ALTER TABLE $t CHANGE $f $f $db_struct{$Conf::Conf{'db_type'}}{$t}{$f} $options");
-			unless ($dbh->do("ALTER TABLE $t CHANGE $f $f $db_struct{$Conf::Conf{'db_type'}}{$t}{$f} $options")) {
-			    &do_log('err', 'Could not change field \'%s\' in table\'%s\'.', $f, $t);
-			    &do_log('err', 'Sympa\'s database structure may have change since last update ; please check RELEASE_NOTES');
-			    return undef;
-			}
-			
-			push @report, sprintf('Field %s in table %s, structure updated', $f, $t);
-			&do_log('info', 'Field %s in table %s, structure updated', $f, $t);
-		    }
-		}else {
-		    unless ($real_struct{$t}{$f} eq $db_struct{$Conf::Conf{'db_type'}}{$t}{$f}) {
-			&do_log('err', 'Field \'%s\'  (table \'%s\' ; database \'%s\') does NOT have awaited type (%s).', $f, $t, $Conf::Conf{'db_name'}, $db_struct{$Conf::Conf{'db_type'}}{$t}{$f});
-			&do_log('err', 'Sympa\'s database structure may have change since last update ; please check RELEASE_NOTES');
-			return undef;
-		    }
 		}
-	    }
+		}
 	    if ($Conf::Conf{'db_type'} eq 'mysql') {
 		## Check that primary key has the right structure.
 		my $should_update;
@@ -1452,7 +1523,7 @@
 		    ## drop previous index
 		    my $success;
 		    foreach my $field (@{$primary{$t}}) {
-			unless ($dbh->do("DROP INDEX $field")) {
+			unless ($dbh->do("DROP INDEX IF EXISTS $field")) {
 			    next;
 			}
 			$success = 1; last;
@@ -1466,7 +1537,7 @@
 		    }
 		    
 		    ## Add INDEX
-		    unless ($dbh->do("CREATE INDEX IF NOT EXIST $t\_index ON $t ($fields)")) {
+		    unless ($dbh->do("CREATE INDEX IF NOT EXISTS $t\_index ON $t ($fields)")) {
 			&do_log('err', 'Could not set INDEX on field \'%s\', table\'%s\'.', $fields, $t);
 			return undef;
 		    }
--- a/src/etc/script/create_db.SQLite
+++ b/src/etc/script/create_db.SQLite
@@ -112,7 +112,7 @@
 	 status_list 	text, 
 	 subject_list 	text, 
 	 topics_list 	text, 
-	 web_archive_list 	integer, 
+	 web_archive_list 	numeric, 
 	 PRIMARY KEY (name_list, robot_list) 
  );
 
