Skip to content
Extraits de code Groupes Projets
sqlite_generique.php 55,5 ko
Newer Older
marcimat's avatar
marcimat a validé
				// possible que d'autres fassent de meme.
				$fields[ trim(strtolower($r[1]),'"') ] = $r[2];
marcimat's avatar
marcimat a validé
			// key inclues dans la requete
			$keys = array();
			foreach(preg_split('/\)\s*,?/',$namedkeys) as $v) {
				if (preg_match("/^\s*([^(]*)\((.*)$/",$v,$r)) {
					$k = str_replace("`", '', trim($r[1]));
					$t = trim(strtolower(str_replace("`", '', $r[2])), '"');
					if ($k && !isset($keys[$k])) $keys[$k] = $t; else $keys[] = $t;
				}
marcimat's avatar
marcimat a validé
			// sinon ajouter les key index
			$query = 
				'SELECT name,sql FROM'
				. ' (SELECT * FROM sqlite_master UNION ALL'
				. ' SELECT * FROM sqlite_temp_master)'
				. " WHERE tbl_name LIKE '$nom_table'"
				. " AND type='index' AND name NOT LIKE 'sqlite_%'"
				. 'ORDER BY substr(type,2,1), name';
			$a = spip_sqlite_query($query, $serveur, $requeter);
			while ($r = spip_sqlite_fetch($a, null, $serveur)) {
marcimat's avatar
marcimat a validé
				$key = str_replace($nom_table.'_','',$r['name']); // enlever le nom de la table ajoute a l'index
				$colonnes = preg_replace(',.*\((.*)\).*,','$1',$r['sql']);
				$keys['KEY '.$key] = $colonnes;
			}			
	// c'est une vue, on liste les champs disponibles simplement
	} else {
		if ($res = sql_fetsel('*',$nom_table,'','','','1','',$serveur)){ // limit 1
			$fields = array();
			foreach($res as $c=>$v) $fields[$c]='';
			$keys = array();		
		} else {	
			return "";	
		}
	return array('field' => $fields, 'key' => $keys);
	
amemo's avatar
amemo a validé
// http://doc.spip.org/@spip_sqlite_update
function spip_sqlite_update($table, $champs, $where='', $desc='', $serveur='',$requeter=true) {
	// recherche de champs 'timestamp' pour mise a jour auto de ceux-ci
	$champs = _sqlite_ajouter_champs_timestamp($table, $champs, $desc, $serveur);
	
	$set = array();
	foreach ($champs as $champ => $val)
		$set[] = $champ . "=$val";
	if (!empty($set))
		return spip_sqlite_query(
			  _sqlite_calculer_expression('UPDATE', $table, ',')
			. _sqlite_calculer_expression('SET', $set, ',')
			. _sqlite_calculer_expression('WHERE', $where), 
amemo's avatar
amemo a validé
// http://doc.spip.org/@spip_sqlite_updateq
function spip_sqlite_updateq($table, $champs, $where='', $desc=array(), $serveur='',$requeter=true) {

	if (!$champs) return;
	if (!$desc) $desc = description_table($table);
	if (!$desc) die("$table insertion sans description");
	$fields =  $desc['field'];
	
	// recherche de champs 'timestamp' pour mise a jour auto de ceux-ci
	$champs = _sqlite_ajouter_champs_timestamp($table, $champs, $desc, $serveur);
	
	$set = array();
	foreach ($champs as $champ => $val) {
		$set[] = $champ . '=' . _sqlite_calculer_cite($val, $fields[$champ]);
	}
	return spip_sqlite_query(
			  _sqlite_calculer_expression('UPDATE', $table, ',')
			. _sqlite_calculer_expression('SET', $set, ',')
			. _sqlite_calculer_expression('WHERE', $where),
}



/*
 * 
 * Ensuite les fonctions non abstraites
 * crees pour l'occasion de sqlite
 * 
 */


// fonction pour la premiere connexion a un serveur SQLite
amemo's avatar
amemo a validé
// http://doc.spip.org/@_sqlite_init
function _sqlite_init(){
	if (!defined('_DIR_DB')) define('_DIR_DB', _DIR_ETC . 'bases/');
	if (!defined('_SQLITE_CHMOD')) define('_SQLITE_CHMOD', _SPIP_CHMOD);
	
	if (!is_dir($d = _DIR_DB)){
		include_spip('inc/flock');
		sous_repertoire($d);
	}
}


// teste la version sqlite du link en cours
amemo's avatar
amemo a validé
// http://doc.spip.org/@_sqlite_is_version
function _sqlite_is_version($version='', $link='', $serveur='',$requeter=true){
	if ($link==='') $link = _sqlite_link($serveur);
	if (!$link) return false;
	if (is_a($link, 'PDO')){
		$v = 3;	
	} else {
		$v = 2;	
	}
	
	if (!$version) return $v;
	return ($version == $v);
}


// retrouver un link (et definir les fonctions externes sqlite->php)
// $recharger devient inutile (a supprimer ?)
amemo's avatar
amemo a validé
// http://doc.spip.org/@_sqlite_link
function _sqlite_link($serveur = '', $recharger = false){
	static $charge = array();
	if ($recharger) $charge[$serveur] = false;
	
	$link = &$GLOBALS['connexions'][$serveur ? $serveur : 0]['link'];

	if ($link && !$charge[$serveur]){
		include_spip('req/sqlite_fonctions');
		_sqlite_init_functions($link);
		$charge[$serveur] = true;
	}
	return $link;
}


/* ordre alphabetique pour les autres */


// renvoie les bons echappements (pas sur les fonctions now())
amemo's avatar
amemo a validé
// http://doc.spip.org/@_sqlite_calculer_cite
function _sqlite_calculer_cite($v, $type) {
	if (sql_test_date($type) AND preg_match('/^\w+\(/', $v))
		return $v;
	if (sql_test_int($type)) {
		if (is_numeric($v))
			return $v;
		if (ctype_xdigit(substr($v,2)) AND strncmp($v,'0x',2)==0)
			return hexdec(substr($v,2));
	}
	//else return  ("'" . spip_sqlite_quote($v) . "'");
	return  (spip_sqlite_quote($v));
}


// renvoie grosso modo "$expression join($join, $v)"
amemo's avatar
amemo a validé
// http://doc.spip.org/@_sqlite_calculer_expression
function _sqlite_calculer_expression($expression, $v, $join = 'AND'){
	if (empty($v))
		return '';
	
	$exp = "\n$expression ";
	
	if (!is_array($v)) {
		return $exp . $v;
	} else {
		if (strtoupper($join) === 'AND')
			return $exp . join("\n\t$join ", array_map('_sqlite_calculer_where', $v));
		else
			return $exp . join($join, $v);
	}
}




// pour conversion 0+x ? (pas la peine en sqlite)
amemo's avatar
amemo a validé
// http://doc.spip.org/@_sqlite_calculer_order
function _sqlite_calculer_order($orderby) {
	return (is_array($orderby)) ? join(", ", $orderby) :  $orderby;
}


// renvoie des 'nom AS alias' 
amemo's avatar
amemo a validé
// http://doc.spip.org/@_sqlite_calculer_select_as
function _sqlite_calculer_select_as($args){
	$res = '';
	foreach($args as $k => $v) {
		if (substr($k,-1)=='@') {
			// c'est une jointure qui se refere au from precedent
			// pas de virgule
		  $res .= '  ' . $v ;
		}
		else {
			if (!is_numeric($k)) {
				$p = strpos($v, " ");
				if ($p)
		  		$v = substr($v,0,$p) . " AS '$k'" . substr($v,$p);
				else $v .= " AS '$k'";
	  	}
	  	$res .= ', ' . $v ;
		}
	return substr($res,2) . $join;
}


// renvoie les bonnes parentheses pour des where imbriquees
amemo's avatar
amemo a validé
// http://doc.spip.org/@_sqlite_calculer_where
function _sqlite_calculer_where($v){
	if (!is_array($v))
	  return $v ;

	$op = array_shift($v);
	if (!($n=count($v)))
		return $op;
	else {
		$arg = _sqlite_calculer_where(array_shift($v));
		if ($n==1) {
			  return "$op($arg)";
		} else {
			$arg2 = _sqlite_calculer_where(array_shift($v));
			if ($n==2) {
				return "($arg $op $arg2)";
			} else return "($arg $op ($arg2) : $v[0])";
		}
	}
}



/*
 * Charger les modules sqlite (si possible) (juste la version demandee),
 * ou, si aucune version, renvoie les versions sqlite dispo 
 * sur ce serveur dans un array
 */
amemo's avatar
amemo a validé
// http://doc.spip.org/@_sqlite_charger_version
function _sqlite_charger_version($version=''){
	$versions = array();
	
	// version 2
	if (!$version || $version == 2){
		if (charger_php_extension('sqlite')) {
			$versions[]=2;
		}
	}
	
	// version 3
	if (!$version || $version == 3){
		if (charger_php_extension('pdo') && charger_php_extension('pdo_sqlite')) {
			$versions[]=3;
		}
	}
	if ($version) return in_array($version, $versions);
	return $versions;
}



 * 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
marcimat's avatar
marcimat a validé
 * ALTER TABLE table ADD|DROP PRIMARY KEY
 * 
 * (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
marcimat's avatar
marcimat a validé
 * 5) remettre les index (qui sont supprimes avec la table A)
 * 
 * @param string/array $table : nom_table, array(nom_table=>nom_futur)
 * @param string/array $col : nom_colonne, array(nom_colonne=>nom_futur)
 * @param array $opt : options comme les tables spip, qui sera merge a la table creee : array('field'=>array('nom'=>'syntaxe', ...), 'key'=>array('KEY nom'=>'colonne', ...))
 * @param string $serveur : nom de la connexion sql en cours
amemo's avatar
amemo a validé
// http://doc.spip.org/@_sqlite_modifier_table
marcimat's avatar
marcimat a validé
function _sqlite_modifier_table($table, $colonne, $opt=array(), $serveur=''){

	if (is_array($table)) {
		$table_origine = array_shift(array_keys($table));
		$table_destination = array_shift($table);
	} else {
		$table_origine = $table_destination = $table;
	}
	// ne prend actuellement qu'un changement
	// mais pourra etre adapte pour changer plus qu'une colonne a la fois
	if (is_array($colonne)) {
		$colonne_origine = array_shift(array_keys($colonne));
		$colonne_destination = array_shift($colonne);
	} else {
		$colonne_origine = $colonne_destination = $colonne;
	}	
	if (!isset($opt['field'])) $opt['field'] = array();
	if (!isset($opt['key'])) $opt['key'] = array();
	
	// 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);
	
marcimat's avatar
marcimat a validé
	$def_origine = sql_showtable($table_origine, false, $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){
marcimat's avatar
marcimat a validé

		if ($colonne_origine && ($c == $colonne_origine)) {
			// si pas DROP
			if ($colonne_destination){
marcimat's avatar
marcimat a validé
				$fields[$colonne_destination] = $opt['field'][$colonne_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){
marcimat's avatar
marcimat a validé
			$fields[$colonne_destination] = $opt['field'][$colonne_destination];
marcimat's avatar
marcimat a validé
	// key...
	$keys = array();
	foreach ($def_origine['key'] as $c=>$d){
marcimat's avatar
marcimat a validé
		$c = str_replace($colonne_origine,$colonne_destination,$c);
		$d = str_replace($colonne_origine,$colonne_destination,$d);
marcimat's avatar
marcimat a validé
		// seulement si on ne supprime pas la colonne !
		if ($d)
			$keys[$c] = $d;
marcimat's avatar
marcimat a validé

	// autres keys, on merge
	$keys = array_merge($keys,$opt['key']);
	$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";
		}
	}
marcimat's avatar
marcimat a validé
	
	// 5) remettre les index !
	foreach ($keys as $k=>$v) {
		if ($k=='PRIMARY KEY'){}
		else {
			// enlever KEY
			$k = substr($k,4);
			$queries[] = "CREATE INDEX $table_destination"."_$k ON $table_destination ($v)";
		}
	}
	
	$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
 */
amemo's avatar
amemo a validé
// http://doc.spip.org/@_sqlite_ref_fonctions
function _sqlite_ref_fonctions(){
	$fonctions = array(
		'alter' => 'spip_sqlite_alter',
		'count' => 'spip_sqlite_count',
		'countsel' => 'spip_sqlite_countsel',
		'create' => 'spip_sqlite_create',
		'create_base' => 'spip_sqlite_create_base',
		'create_view' => 'spip_sqlite_create_view',
		'date_proche' => 'spip_sqlite_date_proche',
		'delete' => 'spip_sqlite_delete',
		'drop_table' => 'spip_sqlite_drop_table',
		'drop_view' => 'spip_sqlite_drop_view',
		'errno' => 'spip_sqlite_errno',
		'error' => 'spip_sqlite_error',
		'explain' => 'spip_sqlite_explain',
		'fetch' => 'spip_sqlite_fetch',
		'free' => 'spip_sqlite_free',
		'hex' => 'spip_sqlite_hex',
		'in' => 'spip_sqlite_in', 
		'insert' => 'spip_sqlite_insert',
		'insertq' => 'spip_sqlite_insertq',
		'insertq_multi' => 'spip_sqlite_insertq_multi',
		'listdbs' => 'spip_sqlite_listdbs',
		'multi' => 'spip_sqlite_multi',
		'optimize' => 'spip_sqlite_optimize',
		'query' => 'spip_sqlite_query',
		'quote' => 'spip_sqlite_quote',
		'replace' => 'spip_sqlite_replace',
		'replace_multi' => 'spip_sqlite_replace_multi',
		'select' => 'spip_sqlite_select',
		'selectdb' => 'spip_sqlite_selectdb',
		'set_charset' => 'spip_sqlite_set_charset',
		'get_charset' => 'spip_sqlite_get_charset',
		'showbase' => 'spip_sqlite_showbase',
		'showtable' => 'spip_sqlite_showtable',
		'update' => 'spip_sqlite_update',
		'updateq' => 'spip_sqlite_updateq',
	);
	
	// association de chaque nom http d'un charset aux couples sqlite 
marcimat's avatar
marcimat a validé
	// SQLite supporte utf-8 et utf-16 uniquement.
	$charsets = array(
		'utf-8'=>array('charset'=>'utf8','collation'=>'utf8_general_ci'), 
		//'utf-16be'=>array('charset'=>'utf16be','collation'=>'UTF-16BE'),// aucune idee de quoi il faut remplir dans es champs la
		//'utf-16le'=>array('charset'=>'utf16le','collation'=>'UTF-16LE')
	);
	
	$fonctions['charsets'] = $charsets;
	
	return $fonctions;
}



// $query est une requete ou une liste de champs
amemo's avatar
amemo a validé
// http://doc.spip.org/@_sqlite_remplacements_definitions_table
function _sqlite_remplacements_definitions_table($query,$autoinc=false){
	$num = "(\s*\([0-9]*\))?";
	$enum = "(\s*\([^\)]*\))?";
	
	$remplace = array(
		'/enum'.$enum.'/is' => 'VARCHAR',
marcimat's avatar
marcimat a validé
		'/binary/is' => '',
cerdic's avatar
cerdic a validé
		'/COLLATE \w+_bin/is' => '',
marcimat's avatar
marcimat a validé
		'/auto_increment/is' => '',
cerdic's avatar
cerdic a validé
		'/(timestamp .* )ON .*$/is' => '\\1',
cerdic's avatar
cerdic a validé
		'/character set \w+/is' => '',
	// pour l'autoincrement, il faut des INTEGER NOT NULL PRIMARY KEY
	if ($autoinc)
		$remplace['/(big|small|medium|tiny)?int(eger)?'.$num.'/is'] = 'INTEGER';

	return preg_replace(array_keys($remplace), $remplace, $query);
}
 * Creer la requete pour la creation d'une table
 * retourne la requete pour utilisation par sql_create() et sql_alter()
amemo's avatar
amemo a validé
// http://doc.spip.org/@_sqlite_requete_create
function _sqlite_requete_create($nom, $champs, $cles, $autoinc=false, $temporary=false, $_ifnotexists=true, $serveur='',$requeter=true) {
	$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;
marcimat's avatar
marcimat a validé
	// sqlite ne gere pas KEY tout court dans une requete CREATE TABLE
	// il faut passer par des create index
	// Il gere par contre primary key !
	// Soit la PK est definie dans les cles, soit dans un champs
	if (!$c = $cles[$pk = "PRIMARY KEY"]) {
		foreach($champs as $k => $v) {
			if (false !== stripos($v,$pk)) {
				$c = $k;
				// on n'en a plus besoin dans field, vu que defini dans key
				$champs[$k] = preg_replace("/$pk/is", '', $champs[$k]); 
				break;	
			}
marcimat's avatar
marcimat a validé
	if ($c) $keys = "\n\t\t$pk ($c)";
	$champs = _sqlite_remplacements_definitions_table($champs, $autoinc);
	foreach($champs as $k => $v) {
		$query .= "$s\n\t\t$k $v";
		$s = ",";
marcimat's avatar
marcimat a validé
	$ifnotexists = "";
	if ($_ifnotexists) {
		// simuler le IF NOT EXISTS - version 2 
		if (_sqlite_is_version(2, '', $serveur)){
marcimat's avatar
marcimat a validé
			$a = spip_sqlite_showtable($nom, $serveur);
marcimat's avatar
marcimat a validé
			if ($a) return false;
		} 
		// sinon l'ajouter en version 3
		else {
			$ifnotexists = ' IF NOT EXISTS';
		}
	$temporary = $temporary ? ' TEMPORARY':'';
marcimat's avatar
marcimat a validé
	$q = "CREATE$temporary TABLE$ifnotexists $nom ($query" . ($keys ? ",$keys" : '') . ")\n";

/*
 * Retrouver les champs 'timestamp'
 * pour les ajouter aux 'insert' ou 'replace'
 * afin de simuler le fonctionnement de mysql 
 * 
 * stocke le resultat pour ne pas faire 
 * de requetes showtable intempestives
 */
amemo's avatar
amemo a validé
// http://doc.spip.org/@_sqlite_ajouter_champs_timestamp
function _sqlite_ajouter_champs_timestamp($table, $couples, $desc='', $serveur=''){
	static $tables = array();
	
	if (!isset($tables[$table])){
		
		if (!$desc){
			$trouver_table = charger_fonction('trouver_table', 'base');
			$desc = $trouver_table($table, $serveur);
			// si pas de description, on ne fait rien, ou on die() ?
			if (!$desc) return $couples;
		}
		
		// recherche des champs avec simplement 'TIMESTAMP'
		// cependant, il faudra peut etre etendre
		// avec la gestion de DEFAULT et ON UPDATE
		// mais ceux-ci ne sont pas utilises dans le core
		$tables[$table] = array();
		foreach ($desc['field'] as $k=>$v){
			if (strpos(strtolower(ltrim($v)), 'timestamp')===0)
	// ajout des champs type 'timestamp' absents
	foreach ($tables[$table] as $maj){
		if (!array_key_exists($maj, $couples))
			$couples[$maj] = "datetime('now')";	
	}
	return $couples;
}
 	
 	

/*
 * renvoyer la liste des versions sqlite disponibles
 * sur le serveur 
 */
amemo's avatar
amemo a validé
// http://doc.spip.org/@spip_versions_sqlite
function spip_versions_sqlite(){
	return 	_sqlite_charger_version();
}




 * Classe pour partager les lancements de requete
 * - peut corriger la syntaxe des requetes pour la conformite a sqlite
 * - peut tracer les requetes
 * 
 * Cette classe est presente essentiellement pour un preg_replace_callback 
 * avec des parametres dans la fonction appelee que l'on souhaite incrementer 
 * (fonction pour proteger les textes)
 * 
 */
class sqlite_traiter_requete{
	var $query = ''; // la requete
	var $queryCount = ''; // la requete pour compter
	var $serveur = ''; // le serveur
	var $link = ''; // le link (ressource) sqlite
	var $prefixe = ''; // le prefixe des tables
	var $db = ''; // le nom de la base 
	var $tracer = false; // doit-on tracer les requetes (var_profile)
	var $sqlite_version = ''; // Version de sqlite (2 ou 3)
	
	// Pour les corrections a effectuer sur les requetes :
	var $textes = array(); 	// array(code=>'texte') trouvé
	var $codeEchappements = "%@##@%";
	
	
	// constructeur
amemo's avatar
amemo a validé
// http://doc.spip.org/@sqlite_traiter_requete
	function sqlite_traiter_requete($query, $serveur = ''){
		$this->query = $query;
		$this->serveur = $serveur;
		
		if (!($this->link = _sqlite_link($this->serveur)) && (!defined('_ECRIRE_INSTALL') || !_ECRIRE_INSTALL)){
			spip_log("Aucune connexion sqlite (link)");
			return false;	
		}

		$this->sqlite_version =_sqlite_is_version('', $this->link);
		
		$this->prefixe 	= $GLOBALS['connexions'][$this->serveur ? $this->serveur : 0]['prefixe'];
		$this->db 		= $GLOBALS['connexions'][$this->serveur ? $this->serveur : 0]['db'];
		
		// tracage des requetes ?
		$this->tracer = (isset($_GET['var_profile']) && $_GET['var_profile']);
	// lancer la requete $this->query,
	// faire le tracage si demande 
amemo's avatar
amemo a validé
// http://doc.spip.org/@executer_requete
		if ($this->tracer) {
			include_spip('public/tracer');
			$t = trace_query_start();
		} else $t = 0 ;
 
marcimat's avatar
marcimat a validé
# spip_log("requete: $this->serveur >> $this->query",'query'); // boum ? pourquoi ?
			// memoriser la derniere erreur PHP vue
			$e = error_get_last();
			// sauver la derniere requete
			$GLOBALS['connexions'][$this->serveur ? $this->serveur : 0]['last'] = $this->query;
			
			if ($this->sqlite_version == 3) {
				$r = $this->link->query($this->query);
				// sauvegarde de la requete (elle y est deja dans $r->queryString)
				# $r->spipQueryString = $this->query;

				// comptage : oblige de compter le nombre d'entrees retournees 
				// par une requete SELECT
				// aucune autre solution ne donne le nombre attendu :( !
				// particulierement s'il y a des LIMIT dans la requete.
				if (strtoupper(substr(ltrim($this->query),0,6)) == 'SELECT'){
						$l = $this->link->query($this->query);
						$r->spipSqliteRowCount =  count($l->fetchAll());
					} elseif (is_a($r, 'PDOStatement')) {
						$r->spipSqliteRowCount = 0;
					}
				}
			} else {
				$r = sqlite_query($this->link, $this->query);
			}

			// loger les warnings/erreurs eventuels de sqlite remontant dans PHP
			if ($err = error_get_last() AND $err!=$e) {
				$err = strip_tags($err['message'])." in ".$err['file']." line ".$err['line'];
				spip_log("$err - ".$this->query, 'sqlite');
			}

esj's avatar
esj a validé

		return $t ? trace_query_end($this->query, $t, $r, $serveur) : $r;
		
	// transformer la requete pour sqlite 
	// enleve les textes, transforme la requete pour quelle soit
	// bien interpretee par sqlite, puis remet les textes
	// la fonction affecte $this->query
amemo's avatar
amemo a validé
// http://doc.spip.org/@traduire_requete
	function traduire_requete(){
		//
		// 1) Protection des textes en les remplacant par des codes
		//
		// enlever les echappements ''
		$this->query = str_replace("''", $this->codeEchappements, $this->query);
		// enlever les 'textes'
		$this->textes = array(); // vider 
		$this->query = preg_replace_callback("/('[^']*')/", array(&$this, '_remplacerTexteParCode'), $this->query);
		
		//
		// 2) Corrections de la requete
		//
		// Correction Create Database
		// Create Database -> requete ignoree
		if (strpos($this->query, 'CREATE DATABASE')===0){
			spip_log("Sqlite : requete non executee -> $this->query","sqlite");
			$this->query = "SELECT 1";	
		
		// Correction Insert Ignore
		// INSERT IGNORE -> insert (tout court et pas 'insert or replace')
		if (strpos($this->query, 'INSERT IGNORE')===0){
			#spip_log("Sqlite : requete transformee -> $this->query","sqlite");
			$this->query = 'INSERT ' . substr($this->query,'13');	
		}
		
		// Correction des dates avec INTERVAL
		// utiliser sql_date_proche() de preference
		if (strpos($this->query, 'INTERVAL')!==false){
			$this->query = preg_replace_callback("/DATE_(ADD|SUB).*INTERVAL\s+(\d+)\s+([a-zA-Z]+)\)/U", 
							array(&$this, '_remplacerDateParTime'), 
							$this->query);
		}
marcimat's avatar
marcimat a validé
		// USING (non reconnu en sqlite2)
		// problematique car la jointure ne se fait pas du coup.
		if (($this->sqlite_version == 2) && (strpos($this->query, "USING")!==false)) {
marcimat's avatar
marcimat a validé
			spip_log("'USING (champ)' n'est pas reconnu en SQLite 2. Utilisez 'ON table1.champ = table2.champ', 'sqlite'");
			$this->query = preg_replace('/USING\s*\([^\)]*\)/', '', $this->query);
		}
		
		// Correction Field
		// remplace FIELD(table,i,j,k...) par CASE WHEN table=i THEN n ... ELSE 0 END
		if (strpos($this->query, 'FIELD')!==false){
			$this->query = preg_replace_callback('/FIELD\s*\(([^\)]*)\)/', 
							array(&$this, '_remplacerFieldParCase'), 
							$this->query); 
		
		// Correction des noms de tables FROM
		// mettre les bons noms de table dans from, update, insert, replace...
		if (preg_match('/\s(SET|VALUES|WHERE|DATABASE)\s/i', $this->query, $regs)) {
			$suite = strstr($this->query, $regs[0]);
			$this->query = substr($this->query, 0, -strlen($suite));
		} else $suite ='';
		$pref = ($this->prefixe) ? $this->prefixe . "_": "";
		$this->query = preg_replace('/([,\s])spip_/', '\1'.$pref, $this->query) . $suite;
marcimat's avatar
marcimat a validé

		// Correction zero AS x
		// pg n'aime pas 0+x AS alias, sqlite, dans le meme style, 
		// n'apprecie pas du tout SELECT 0 as x ... ORDER BY x
		// il dit que x ne doit pas être un integer dans le order by !
		// on remplace du coup x par vide() dans ce cas uniquement
		//
		// rien que pour public/vertebrer.php ?
		if ((strpos($this->query, "0 AS")!==false)){
			// on ne remplace que dans ORDER BY ou GROUP BY
			if (preg_match('/\s(ORDER|GROUP) BY\s/i', $this->query, $regs)) {
				$suite = strstr($this->query, $regs[0]);
				$this->query = substr($this->query, 0, -strlen($suite));
			
				// on cherche les noms des x dans 0 AS x
				// on remplace dans $suite le nom par vide()
				preg_match_all('/\b0 AS\s*([^\s,]+)/', $this->query, $matches, PREG_PATTERN_ORDER);
				foreach ($matches[1] as $m){
					$suite = str_replace($m, 'VIDE()', $suite);
				}
				$this->query .= $suite;
			}
		}
marcimat's avatar
marcimat a validé

		// Correction possible des divisions entieres
		// Le standard SQL (lequel? ou?) semble indiquer que
		// a/b=c doit donner c entier si a et b sont entiers 4/3=1.
		// C'est ce que retournent effectivement SQL Server et SQLite
		// Ce n'est pas ce qu'applique MySQL qui retourne un reel : 4/3=1.333...
		// 
		// On peut forcer la conversion en multipliant par 1.0 avant la division
		// /!\ SQLite 3.5.9 Debian/Ubuntu est victime d'un bug en plus ! 
		// cf. https://bugs.launchpad.net/ubuntu/+source/sqlite3/+bug/254228
		//     http://www.sqlite.org/cvstrac/tktview?tn=3202
		// (4*1.0/3) n'est pas rendu dans ce cas !
		# $this->query = str_replace('/','* 1.00 / ',$this->query);
			
		// Correction Antiquotes
		// ` => rien
		$this->query = str_replace('`','',$this->query);
		
		// Correction critere REGEXP, non reconnu en sqlite2
		if (($this->sqlite_version == 2) && (strpos($this->query, 'REGEXP')!==false)){
			$this->query = preg_replace('/([^\s\(]*)(\s*)REGEXP(\s*)([^\s\)]*)/', 'REGEXP($4, $1)', $this->query);
		}
		
		
		//
		// 3) Remise en place des textes d'origine
		//
		// remettre les 'textes'
		foreach ($this->textes as $cle=>$val){
			$this->query = str_replace($cle, $val, $this->query);
		}
		// remettre les echappements ''
		$this->query = str_replace($this->codeEchappements,"''",$this->query);
	// remplacer DATE_ / INTERVAL par DATE...strtotime
amemo's avatar
amemo a validé
// http://doc.spip.org/@_remplacerDateParTime
	function _remplacerDateParTime($matches){
		$op = strtoupper($matches[1] == 'ADD')?'+':'-';	
		return "datetime('" . date("Y-m-d H:i:s") . "', '$op$matches[2] $matches[3]')";

	// callback ou l'on remplace FIELD(table,i,j,k...) par CASE WHEN table=i THEN n ... ELSE 0 END
amemo's avatar
amemo a validé
// http://doc.spip.org/@_remplacerFieldParCase
	function _remplacerFieldParCase($matches){
		$fields = substr($matches[0],6,-1); // ne recuperer que l'interieur X de field(X)
		$t = explode(',', $fields);
		$index = array_shift($t);

		$res = '';
		$n=0;
		foreach($t as $v) {
			$n++;
			$res .= "\nWHEN $index=$v THEN $n";
		}
		return "CASE $res ELSE 0 END ";			
	}

	// callback ou l'on sauve le texte qui est cache dans un tableau $this->textes
amemo's avatar
amemo a validé
// http://doc.spip.org/@_remplacerTexteParCode
	function _remplacerTexteParCode($matches){
		$this->textes[$code = "%@##".count($this->textes)."##@%"] = $matches[1];
		return $code;	
	}