Valider 4974f9f2 rédigé par marcimat's avatar marcimat
Parcourir les fichiers

Améliorer la gestion des ALTER TABLE table sous SQLITE

- 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.
parent 86535f9b
Chargement en cours
Chargement en cours
Chargement en cours
Chargement en cours
+246 −168
Numéro de ligne d'origine Numéro de ligne de diff Ligne de diff
@@ -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,28 +207,63 @@ 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:
				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|RENAME) : $query", 'sqlite');
						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");
@@ -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";
@@ -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
 */
@@ -982,98 +1088,70 @@ function _sqlite_remplacements_definitions_table($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';

	// 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)
function _sqlite_requete_create($nom, $champs, $cles, $autoinc=false, $temporary=false, $_ifnotexists=true, $serveur='') {
	$query = $keys = $s = $p = '';

	// 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;						
	}
	$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]));
			}
		}
		*/
		
	// 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;						
		// 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);