From 4974f9f21fbdcb2226edb17c30c2f58e49230b13 Mon Sep 17 00:00:00 2001
From: Matthieu Marcillaud <marcimat@rezo.net>
Date: Fri, 14 Dec 2007 00:36:33 +0000
Subject: [PATCH] =?UTF-8?q?Am=C3=A9liorer=20la=20gestion=20des=20ALTER=20T?=
 =?UTF-8?q?ABLE=20table=20=20sous=20SQLITE?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

- SQLite2 ne prenait pas en compte (ADD|RENAME) column !
Il faut sous sqlite2 comme pour DROP, MODIFY ou CHANGE, créer une autre table qui contient les changements.

- Les opérations sur DROP, MODIFY, CHANGE, aunsi que pour SQLite2 ADD et RENAME se font maintenant de manière transactionnelle (BEGIN TRANSACTION; les requetes; COMMIT;) de tel sorte que si une erreur survient au cours d'une des requetes, aucun changement ne sera pris en compte.
---
 ecrire/req/sqlite_generique.php | 414 +++++++++++++++++++-------------
 1 file changed, 246 insertions(+), 168 deletions(-)

diff --git a/ecrire/req/sqlite_generique.php b/ecrire/req/sqlite_generique.php
index 6dfec661fe..f60830e97b 100644
--- a/ecrire/req/sqlite_generique.php
+++ b/ecrire/req/sqlite_generique.php
@@ -147,10 +147,10 @@ function spip_sqlite_alter($query, $serveur=''){
 	 */
 	
 	// 1
-	if (preg_match("/\s*ALTER(\s*IGNORE)?\s*TABLE\s*([^\s]*)\s*/i", $query, $regs)){
-		$debut = $regs[0];
-		$table = $regs[2];
-		$suite = substr($query, strlen($debut));
+	if (preg_match("/\s*(ALTER(\s*IGNORE)?\s*TABLE\s*([^\s]*))\s*(.*)?/i", $query, $regs)){
+		$debut = $regs[1];
+		$table = $regs[3];
+		$suite = $regs[4];
 	} else {
 		spip_log("SQLite : Probleme de ALTER TABLE mal forme dans $query", 'sqlite');
 		return false;
@@ -163,7 +163,7 @@ function spip_sqlite_alter($query, $serveur=''){
 	$resultats = array();
 	foreach ($todo as $do){
 		$do = trim($do);
-		if (!preg_match('/(DROP|CHANGE COLUMN|CHANGE|MODIFY|RENAME TO|ADD COLUMN|ADD)\s*([^\s]*)\s*(.*)?/', $do, $matches)){
+		if (!preg_match('/(DROP|CHANGE COLUMN|CHANGE|MODIFY|RENAME TO|RENAME|ADD COLUMN|ADD)\s*([^\s]*)\s*(.*)?/', $do, $matches)){
 			spip_log("SQLite : Probleme de ALTER TABLE, utilisation non reconnue dans : $query", 'sqlite');
 			return false;				
 		}
@@ -177,11 +177,11 @@ function spip_sqlite_alter($query, $serveur=''){
 		switch($cle){
 			// allez, on simule, on simule !
 			case 'DROP':
-				if (!_sqlite_traiter_alter_table(
+				if (!_sqlite_modifier_table(
+					$table, 
 					$table, 
-					'DROP', 
 					$colonne_origine, 
-					'', 
+					'', // colonne origine -> rien !
 					'', 
 					$serveur)){
 						return false;		
@@ -195,9 +195,9 @@ function spip_sqlite_alter($query, $serveur=''){
 				$colonne_destination = substr($def, 0, strpos($def,' '));
 				$def = substr($def, strlen($colonne_destination)+1);
 				
-				if (!_sqlite_traiter_alter_table(
+				if (!_sqlite_modifier_table(
+					$table, 
 					$table, 
-					'CHANGE', 
 					$colonne_origine, 
 					$colonne_destination, 
 					$def, 
@@ -207,33 +207,68 @@ function spip_sqlite_alter($query, $serveur=''){
 				break;
 				
 			case 'MODIFY':
-				if (!_sqlite_traiter_alter_table(
+				if (!_sqlite_modifier_table(
 					$table, 
-					'CHANGE', 
+					$table, 
+					$colonne_origine, 
 					$colonne_origine, 
-					$colonne_origine, // un change sur la meme colonne 
 					$def, 
 					$serveur)){
 						return false;		
 				}	
 				break;
 			
-			// RAS pour ceux la
+			// pas geres en sqlite2
+			case 'RENAME':
+					$do = "RENAME TO" . substr($do,6);
 			case 'RENAME TO':
+				if (_sqlite_is_version(3, '', $serveur)){
+					$requete = new sqlite_traiter_requete("$debut $do", $serveur);
+					if (!$requete->executer_requete()){
+						spip_log("SQLite : Erreur ALTER TABLE / RENAME : $query", 'sqlite');
+						return false;
+					}
+				// artillerie lourde pour sqlite2 !
+				} else {
+					$table_dest = trim(substr($do, 9));
+					if (!_sqlite_modifier_table($table, $table_dest, '', '', '', $serveur)){
+						spip_log("SQLite : Erreur ALTER TABLE / RENAME : $query", 'sqlite');
+						return false;		
+					}					
+				}		
+				break;
+				
+			// pas geres en sqlite2
 			case 'ADD COLUMN':
+					$do = "ADD".substr($do, 10);
 			case 'ADD':
 			default:
-				$requete = new sqlite_traiter_requete("$debut $do", $serveur);
-				if (!$requete->executer_requete()){
-					spip_log("SQLite : Erreur ALTER TABLE (ADD|RENAME) : $query", 'sqlite');
-					return false;
-				}
-				break;
+				if (_sqlite_is_version(3, '', $serveur)){
+					$requete = new sqlite_traiter_requete("$debut $do", $serveur);
+					if (!$requete->executer_requete()){
+						spip_log("SQLite : Erreur ALTER TABLE / ADD : $query", 'sqlite');
+						return false;
+					}
+					break;
+				// artillerie lourde pour sqlite2 !
+				} else {
+					if (preg_match('/^(.*)(BEFORE|AFTER)(.*)$/is', $do, $matches)) {
+						$do = $matches[1];
+					}
+					$def = trim(substr($do, 3));
+					$colonne_ajoutee = substr($def, 0, strpos($def,' '));
+					$def = substr($def, strlen($colonne_ajoutee)+1);
+					if (!_sqlite_modifier_table($table, $table, '', $colonne_ajoutee, $def, $serveur)){
+						spip_log("SQLite : Erreur ALTER TABLE / ADD : $query", 'sqlite');
+						return false;		
+					}					
+				}		
+				break;				
 		}
 		// tout est bon, ouf !
 		spip_log("SQLite ($serveur) : Changements OK : $debut $do");
 	}
-	
+
 	spip_log("SQLite ($serveur) : fin ALTER TABLE OK !");
 	return true;
 }
@@ -241,67 +276,9 @@ function spip_sqlite_alter($query, $serveur=''){
 
 // Fonction de creation d'une table SQL nommee $nom
 function spip_sqlite_create($nom, $champs, $cles, $autoinc=false, $temporary=false, $serveur='') {
-
-	$query = $keys = $s = $p = '';
-
-	// certains plugins declarent les tables  (permet leur inclusion dans le dump)
-	// sans les renseigner (laisse le compilo recuperer la description)
-	if (!is_array($champs) || !is_array($cles)) 
-		return;
-
-	// sqlite ne gere pas KEY tout court
-	foreach($cles as $k => $v) {
-		if ($k == "PRIMARY KEY"){
-			$keys .= "$s\n\t\t$k ($v)";
-			$p = $v;
-		}
-		$s = ",";
-	}
-	$s = '';
-	
-	/* a tester ulterieurement
-	 * je ne sais pas a quoi ca sert
-	 *
-	$character_set = "";
-	if (@$GLOBALS['meta']['charset_sql_base'])
-		$character_set .= " CHARACTER SET ".$GLOBALS['meta']['charset_sql_base'];
-	if (@$GLOBALS['meta']['charset_collation_sql_base'])
-		$character_set .= " COLLATE ".$GLOBALS['meta']['charset_collation_sql_base'];
-	*/
-
-	
-	$champs = _sqlite_remplacements_definitions_table($champs);
-
-	foreach($champs as $k => $v) {
-		// je sais pas ce que c'est ca...
-		// puis personne rentre ici vue qe binary->''
-		/*
-		if (preg_match(',([a-z]*\s*(\(\s*[0-9]*\s*\))?(\s*binary)?),i',$v,$defs)){
-			if (preg_match(',(char|text),i',$defs[1]) AND !preg_match(',binary,i',$defs[1]) ){
-				$v = $defs[1] . $character_set . ' ' . substr($v,strlen($defs[1]));
-			}
-		}
-		*/
-		
-		// autoincrement v3.1.3 ?
-		$query .= "$s\n\t\t$k $v";
-			//. (($autoinc && ($p == $k) && preg_match(',\binteger\b,i', $v))? " AUTOINCREMENT": '');
-		$s = ",";
-	}
-
-	/* simuler le IF NOT EXISTS - version 2 */
-	if (_sqlite_is_version(2, '', $serveur)){
-		$a = spip_sqlite_showtable($nom, $serveur); 
-		if ($a) return false;
-	}
-	
-	$temporary = $temporary ? ' TEMPORARY':'';
-	$ifnotexists = _sqlite_is_version(3, '', $serveur) ? ' IF NOT EXISTS':'';// IF NOT EXISTS 
-	$q = "CREATE$temporary TABLE$ifnotexists $nom ($query" . ($keys ? ",$keys" : '') . ")"
-	//. ($character_set?" DEFAULT $character_set":"")
-	."\n";
-
-	return spip_sqlite_query($q, $serveur);
+	$query = _sqlite_requete_create($nom, $champs, $cles, $autoinc, $temporary, $ifnotexists=true, $serveur);
+	if (!$query) return false;
+	return spip_sqlite_query($query, $serveur);
 }
 
 
@@ -403,6 +380,7 @@ function spip_sqlite_explain($query, $serveur=''){
 
 
 function spip_sqlite_fetch($r, $t='', $serveur='') {
+
 	$link = _sqlite_link($serveur);
 	if (!$t) {
 		if (_sqlite_is_version(3, $link)) {
@@ -486,6 +464,7 @@ function spip_sqlite_insert($table, $champs, $valeurs, $desc='', $serveur='') {
 
 	if ($prefixe) $table = preg_replace('/^spip/', $prefixe, $table);
 
+
 	$t = !isset($_GET['var_profile']) ? 0 : trace_query_start();
 
 	$query="INSERT OR REPLACE INTO $table $champs VALUES $valeurs";
@@ -656,7 +635,7 @@ function spip_sqlite_showtable($nom_table, $serveur=''){
 
 	$query = 
 			'SELECT sql FROM'
-   			. '(SELECT * FROM sqlite_master UNION ALL'
+   			. ' (SELECT * FROM sqlite_master UNION ALL'
    			. ' SELECT * FROM sqlite_temp_master)'
 			. " WHERE tbl_name LIKE '$nom_table'"
 			. " AND type!='meta' AND sql NOT NULL AND name NOT LIKE 'sqlite_%'"
@@ -907,6 +886,133 @@ function _sqlite_charger_version($version=''){
 
 
 
+/*
+ * Gestion des requetes ALTER non reconnues de SQLite :
+ * ALTER TABLE table DROP column
+ * ALTER TABLE table CHANGE [COLUMN] columnA columnB definition
+ * ALTER TABLE table MODIFY column definition
+ * 
+ * (MODIFY transforme en CHANGE columnA columnA) par spip_sqlite_alter()
+ * 
+ * 1) creer une table B avec le nouveau format souhaite
+ * 2) copier la table d'origine A vers B
+ * 3) supprimer la table A
+ * 4) renommer la table B en A
+ * 
+ */
+function _sqlite_modifier_table($table_origine, $table_destination, $colonne_origine='', $colonne_destination='', $def_col_destination='', $serveur=''){
+	
+	// si les noms de tables sont differents, pas besoin de table temporaire
+	// on prendra directement le nom de la future table
+	$meme_table = ($table_origine == $table_destination);
+	
+	$def_origine = sql_showtable($table_origine, $serveur);
+	$table_tmp = $table_origine . '_tmp';
+
+	// 1) creer une table temporaire avec les modifications	
+	// - DROP : suppression de la colonne
+	// - CHANGE : modification de la colonne
+	// (foreach pour conserver l'ordre des champs)
+	
+	// field 
+	$fields = array();
+	// pour le INSERT INTO plus loin
+	// stocker la correspondance nouvelles->anciennes colonnes
+	$fields_correspondances = array(); 
+
+	foreach ($def_origine['field'] as $c=>$d){
+		if ($colonne_origine && ($c == $colonne_origine)) {
+			// si pas DROP
+			if ($colonne_destination){
+				$fields[$colonne_destination] = $def_col_destination;
+				$fields_correspondances[$colonne_destination] = $c;
+			}	
+		} else {
+			$fields[$c] = $d;
+			$fields_correspondances[$c] = $c;
+		}
+	}
+	// cas de ADD sqlite2 (ajout du champ en fin de table):
+	if (!$colonne_origine && $colonne_destination){
+			$fields[$colonne_destination] = $def_col_destination;
+	}
+	
+	// key
+	$keys = array();
+	foreach ($def_origine['key'] as $c=>$d){
+		if ($d == $colonne_origine) {
+			if ($colonne_destination){ 
+				$keys[$c] = $colonne_destination;
+			}	
+		} else {
+			$keys[$c] = $d;
+		}
+	}
+	
+	$queries = array();
+	$queries[] = 'BEGIN TRANSACTION';
+	
+	// copier dans destination (si differente de origine), sinon tmp
+	$table_copie = ($meme_table) ? $table_tmp : $table_destination;
+	
+	if ($q = _sqlite_requete_create(
+			$table_copie, 
+			$fields, 
+			$keys, 
+			$autoinc=false,
+			$temporary=false, 
+			$ifnotexists=true,
+			$serveur)){
+		$queries[] = $q;			
+	}
+
+	
+	// 2) y copier les champs qui vont bien
+	$champs_dest = join(', ', array_keys($fields_correspondances));
+	$champs_ori = join(', ', $fields_correspondances);
+	$queries[] = "INSERT INTO $table_copie ($champs_dest) SELECT $champs_ori FROM $table_origine";
+		
+	// 3) supprimer la table d'origine
+	$queries[] = "DROP TABLE $table_origine";
+	
+	// 4) renommer la table temporaire 
+	// avec le nom de la table destination
+	// si necessaire
+	if ($meme_table){
+		if (_sqlite_is_version(3, '', $serveur)){
+			$queries[] = "ALTER TABLE $table_copie RENAME TO $table_destination";
+		} else {
+			$queries[] = _sqlite_requete_create(
+					$table_destination, 
+					$fields, 
+					$keys, 
+					$autoinc=false,
+					$temporary=false, 
+					$ifnotexists=false, // la table existe puisqu'on est dans une transaction
+					$serveur);	
+			$queries[] = "INSERT INTO $table_destination SELECT * FROM $table_copie";		
+			$queries[] = "DROP TABLE $table_copie";
+		}
+	}
+	$queries[] = "COMMIT";
+	
+
+	// il faut les faire une par une car $query = join('; ', $queries).";"; ne fonctionne pas
+	foreach ($queries as $q){
+		$req = new sqlite_traiter_requete($q, $serveur);
+		if (!$req->executer_requete()){	
+			spip_log("SQLite : ALTER TABLE table :" 
+			." Erreur a l'execution de la requete : $q",'sqlite'); 
+			return false;
+		}
+	}
+
+	return true;					
+}
+
+
+
+
 /*
  * Nom des fonctions
  */
@@ -979,103 +1085,75 @@ function _sqlite_remplacements_definitions_table($query){
 
 	return preg_replace(array_keys($remplace), $remplace, $query);
 }
-	
+
 
 /*
- * Gestion des requetes ALTER non reconnues de SQLite :
- * ALTER TABLE table DROP column
- * ALTER TABLE table CHANGE [COLUMN] columnA columnB definition
- * ALTER TABLE table MODIFY column definition
- * 
- * (MODIFY transforme en CHANGE columnA columnA) par spip_sqlite_alter()
- * 
- * 1) creer une table B avec le nouveau format souhaite
- * 2) copier la table d'origine A vers B
- * 3) supprimer la table A
- * 4) renommer la table B en A
- * 
+ * Creer la requete pour la creation d'une table
+ * retourne la requete pour utilisation par sql_create() et sql_alter()
  */
-function _sqlite_traiter_alter_table($table, $ordre, $colonne_origine, $colonne_destination='', $def_col_destination='', $serveur=''){
-	
-	$def_origine = sql_showtable($table, $serveur);
-	$table_tmp = $table . '_tmp';
+function _sqlite_requete_create($nom, $champs, $cles, $autoinc=false, $temporary=false, $_ifnotexists=true, $serveur='') {
+	$query = $keys = $s = $p = '';
 
-	// 1) creer une table temporaire avec les modifications	
-	// - DROP : suppression de la colonne
-	// - CHANGE : modification de la colonne
-	// (foreach pour conserver l'ordre des champs)
-	
-	// field 
-	$fields = array();
-	// pour le INSERT INTO plus loin
-	// stocker la correspondance nouvelles->anciennes colonnes
-	$fields_correspondances = array(); 
+	// certains plugins declarent les tables  (permet leur inclusion dans le dump)
+	// sans les renseigner (laisse le compilo recuperer la description)
+	if (!is_array($champs) || !is_array($cles)) 
+		return;
 
-	foreach ($def_origine['field'] as $c=>$d){
-		if ($c == $colonne_origine) {
-			// si pas DROP
-			if ($colonne_destination){
-				$fields[$colonne_destination] = $def_col_destination;
-				$fields_correspondances[$colonne_destination] = $c;
-			}	
-		} else {
-			$fields[$c] = $d;
-			$fields_correspondances[$c] = $c;
-		}
-	}
-	// key
-	$keys = array();
-	foreach ($def_origine['key'] as $c=>$d){
-		if ($d == $colonne_origine) {
-			if ($colonne_destination){ 
-				$keys[$c] = $colonne_destination;
-			}	
-		} else {
-			$keys[$c] = $d;
+	// sqlite ne gere pas KEY tout court
+	foreach($cles as $k => $v) {
+		if ($k == "PRIMARY KEY"){
+			$keys .= "$s\n\t\t$k ($v)";
+			$p = $v;
 		}
+		$s = ",";
 	}
+	$s = '';
 	
+	/* a tester ulterieurement
+	 * je ne sais pas a quoi ca sert
+	 *
+	$character_set = "";
+	if (@$GLOBALS['meta']['charset_sql_base'])
+		$character_set .= " CHARACTER SET ".$GLOBALS['meta']['charset_sql_base'];
+	if (@$GLOBALS['meta']['charset_collation_sql_base'])
+		$character_set .= " COLLATE ".$GLOBALS['meta']['charset_collation_sql_base'];
+	*/
 
-	if (!sql_create(
-			$table_tmp, 
-			$fields, 
-			$keys, 
-			$autoinc=false,
-			$temporary=false, 
-			$serveur)){
-				spip_log("SQLite : ALTER TABLE table $ordre column :"
-					.' La creation de la table temporaire a echouee','sqlite');
-				// si on arrive la, on est plutot mal barre !
-				return false;
-	}
-	
-	// 2) y copier les champs qui vont bien
-	$champs_dest = join(', ', array_keys($fields_correspondances));
-	$champs_ori = join(', ', $fields_correspondances);
-	if (!sql_query("INSERT INTO $table_tmp ($champs_dest) SELECT $champs_ori FROM $table")){
-		spip_log("SQLite : ALTER TABLE table $ordre column :"
-				.' La copie de la table d\'origine vers la table temporaire a echouee','sqlite');
-		return false;						
-	}
-	
 	
-	// 3) supprimer la table d'origine
-	if (!sql_query("DROP TABLE $table")){
-		spip_log("SQLite : ALTER TABLE table $ordre column :"
-				.' La suppression de la table d\'origine a echouee','sqlite');
-		return false;						
+	$champs = _sqlite_remplacements_definitions_table($champs);
+
+	foreach($champs as $k => $v) {
+		// je sais pas ce que c'est ca...
+		// puis personne rentre ici vue qe binary->''
+		/*
+		if (preg_match(',([a-z]*\s*(\(\s*[0-9]*\s*\))?(\s*binary)?),i',$v,$defs)){
+			if (preg_match(',(char|text),i',$defs[1]) AND !preg_match(',binary,i',$defs[1]) ){
+				$v = $defs[1] . $character_set . ' ' . substr($v,strlen($defs[1]));
+			}
+		}
+		*/
+		
+		// autoincrement v3.1.3 ?
+		$query .= "$s\n\t\t$k $v";
+			//. (($autoinc && ($p == $k) && preg_match(',\binteger\b,i', $v))? " AUTOINCREMENT": '');
+		$s = ",";
 	}
-	
-	// 4) renommer la table temporaire avec le nom de la table d'origine
-	if (!sql_query("ALTER TABLE $table_tmp RENAME TO $table")){
-		spip_log("SQLite : ALTER TABLE table $ordre column :"
-				.' Le renommage de la table temporaire avec le nom de la table d\'origine a echoue','sqlite');
-		return false;						
+
+	/* simuler le IF NOT EXISTS - version 2 */
+	if ($_ifnotexists && _sqlite_is_version(2, '', $serveur)){
+		$a = spip_sqlite_showtable($nom, $serveur); 
+		if ($a) return false;
 	}
-		
-	return true;					
-}
 
+	$temporary = $temporary ? ' TEMPORARY':'';
+	$ifnotexists = ($_ifnotexists && _sqlite_is_version(3, '', $serveur)) ? ' IF NOT EXISTS':'';// IF NOT EXISTS 
+	$q = "CREATE$temporary TABLE$ifnotexists $nom ($query" . ($keys ? ",$keys" : '') . ")"
+	//. ($character_set?" DEFAULT $character_set":"")
+	."\n";
+
+	return $q;
+}
+	
 
 	
 /*
@@ -1131,7 +1209,7 @@ class sqlite_traiter_requete{
 	/* lancer la requete $this->requete et faire le tracage si demande */
 	function executer_requete(){
 		$t = !isset($_GET['var_profile']) ? 0 : trace_query_start();
-		#echo("<br /><b>spip_sqlite_query() $serveur >></b> $query"); // boum ? pourquoi ?
+		//echo("<br /><b>executer_requete() $this->serveur >></b> $this->query"); // boum ? pourquoi ?
 		if ($this->link){
 			if (_sqlite_is_version(3, $this->link)) {
 				$r = $this->link->query($this->query);
-- 
GitLab