toto()->toto sinon php4 se tue ! # todo : get/set_caracteres ? /* * * regroupe le maximum de fonctions qui peuvent cohabiter * D'abord les fonctions d'abstractions de SPIP * */ // http://doc.spip.org/@req_sqlite_dist function req_sqlite_dist($addr, $port, $login, $pass, $db='', $prefixe='', $sqlite_version=''){ static $last_connect = array(); // si provient de selectdb // un code pour etre sur que l'on vient de select_db() if (strpos($db, $code = '@selectdb@')!==false) { foreach (array('addr','port','login','pass','prefixe') as $a){ $$a = $last_connect[$a]; } $db = str_replace($code, '', $db); } /* * En sqlite, seule l'adresse du fichier est importante. * Ce sera $db le nom, et le path _DIR_DB */ _sqlite_init(); // un nom de base demande et impossible d'obtenir la base, on s'en va if ($db && !is_file($f = _DIR_DB . $db . '.sqlite') && !is_writable(_DIR_DB)) return false; // charger les modules sqlite au besoin if (!_sqlite_charger_version($sqlite_version)) { spip_log("Impossible de trouver/charger le module SQLite ($sqlite_version)!"); return false; } // chargement des constantes // il ne faut pas definir les constantes avant d'avoir charge les modules sqlite $define = "spip_sqlite".$sqlite_version."_constantes"; $define(); $ok = false; if (!$db){ // si installation -> base temporaire tant qu'on ne connait pas son vrai nom if (defined('_ECRIRE_INSTALL') && _ECRIRE_INSTALL){ // creation d'une base temporaire pour le debut d'install $db = "_sqlite".$sqlite_version."_install"; $tmp = _DIR_DB . $db . ".sqlite"; if ($sqlite_version == 3) { $ok = $link = new PDO("sqlite:$tmp"); } else { $ok = $link = sqlite_open($tmp, _SQLITE_CHMOD, $err); } // sinon, on arrete finalement } else { return false; } } else { // Ouvrir (eventuellement creer la base) // si pas de version fourni, on essaie la 3, sinon la 2 if ($sqlite_version == 3) { $ok = $link = new PDO("sqlite:$f"); } else { $ok = $link = sqlite_open($f, _SQLITE_CHMOD, $err); } } if (!$ok){ spip_log("Impossible d'ouvrir la base de donnee SQLite ($sqlite_version) : $f "); return false; } if ($link) { $last_connect = array ( 'addr' => $addr, 'port' => $port, 'login' => $login, 'pass' => $pass, 'db' => $db, 'prefixe' => $prefixe, ); } return array( 'db' => $db, 'prefixe' => $prefixe ? $prefixe : $db, 'link' => $link, ); } // Fonction de requete generale, munie d'une trace a la demande // http://doc.spip.org/@spip_sqlite_query function spip_sqlite_query($query, $serveur='',$requeter=true) { #spip_log("spip_sqlite_query() > $query"); _sqlite_init(); $requete = new sqlite_traiter_requete($query, $serveur); $requete->traduire_requete(); // mysql -> sqlite if (!$requeter) return $requete->query; return $requete->executer_requete(); } /* ordre alphabetique pour les autres */ // http://doc.spip.org/@spip_sqlite_alter function spip_sqlite_alter($query, $serveur='',$requeter=true){ $query = spip_sqlite_query("ALTER $query",$serveur,false); /* * la il faut faire les transformations * si ALTER TABLE x (DROP|CHANGE) y * * 1) recuperer "ALTER TABLE table " * 2) spliter les sous requetes (,) * 3) faire chaque requete independemment */ // 1 if (preg_match("/\s*(ALTER(\s*IGNORE)?\s*TABLE\s*([^\s]*))\s*(.*)?/is", $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; } // 2 // il faudrait une regexp pour eviter de spliter ADD PRIMARY KEY (colA, colB) // tout en cassant "ADD PRIMARY KEY (colA, colB), ADD INDEX (chose)"... en deux // ou revoir l'api de sql_alter en creant un // sql_alter_table($table,array($actions)); $todo = explode(',', $suite); // on remet les morceaux dechires ensembles... que c'est laid ! $todo2 = array(); $i=0; $ouverte = false; while ($do = array_shift($todo)) { $todo2[$i] = isset($todo2[$i]) ? $todo2[$i] . "," . $do : $do; $o=(false!==strpos($do,"(")); $f=(false!==strpos($do,")")); if ($o AND !$f) $ouverte=true; elseif ($f) $ouverte=false; if (!$ouverte) $i++; } // 3 $resultats = array(); foreach ($todo2 as $do){ $do = trim($do); if (!preg_match('/(DROP PRIMARY KEY|DROP INDEX|DROP COLUMN|DROP' .'|CHANGE COLUMN|CHANGE|MODIFY|RENAME TO|RENAME' .'|ADD PRIMARY KEY|ADD INDEX|ADD COLUMN|ADD' .')\s*([^\s]*)\s*(.*)?/', $do, $matches)){ spip_log("SQLite : Probleme de ALTER TABLE, utilisation non reconnue dans : $do \n(requete d'origine : $query)", 'sqlite'); return false; } $cle = strtoupper($matches[1]); $colonne_origine = $matches[2]; $colonne_destination = ''; $def = $matches[3]; // eluder une eventuelle clause before|after|first inutilisable $defr = rtrim(preg_replace('/(BEFORE|AFTER|FIRST)(.*)$/is','', $def)); // remplacer les definitions venant de mysql $defr = _sqlite_remplacements_definitions_table($defr); // reinjecter dans le do $do = str_replace($def,$defr,$do); $def = $defr; switch($cle){ // suppression d'un index case 'DROP INDEX': $nom_index = $colonne_origine; spip_sqlite_drop_index($nom_index, $table, $serveur); break; // suppression d'une pk case 'DROP PRIMARY KEY': if (!_sqlite_modifier_table( $table, $colonne_origine, array('key'=>array('PRIMARY KEY'=>'')), $serveur)){ return false; } break; // suppression d'une colonne case 'DROP COLUMN': case 'DROP': if (!_sqlite_modifier_table( $table, array($colonne_origine=>""), '', $serveur)){ return false; } break; case 'CHANGE COLUMN': case 'CHANGE': // recuperer le nom de la future colonne $def = trim($def); $colonne_destination = substr($def, 0, strpos($def,' ')); $def = substr($def, strlen($colonne_destination)+1); if (!_sqlite_modifier_table( $table, array($colonne_origine=>$colonne_destination), array('field'=>array($colonne_destination=>$def)), $serveur)){ return false; } break; case 'MODIFY': if (!_sqlite_modifier_table( $table, $colonne_origine, array('field'=>array($colonne_origine=>$def)), $serveur)){ return false; } break; // 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(array($table=>$table_dest), '', '', $serveur)){ spip_log("SQLite : Erreur ALTER TABLE / RENAME : $query", 'sqlite'); return false; } } break; // ajout d'une pk case 'ADD PRIMARY KEY': $pk = trim(substr($do,16)); $pk = ($pk[0]=='(') ? substr($pk,1,-1) : $pk; if (!_sqlite_modifier_table( $table, $colonne_origine, array('key'=>array('PRIMARY KEY'=>$pk)), $serveur)){ return false; } break; // ajout d'un index case 'ADD INDEX': // peut etre "(colonne)" ou "nom_index (colonnes)" // bug potentiel si qqn met "(colonne, colonne)" // // nom_index (colonnes) if ($def) { $colonnes = substr($def,1,-1); $nom_index = $colonne_origine; } else { // (colonne) if ($colonne_origine[0] == "(") { $colonnes = substr($colonne_origine,1,-1); if (false!==strpos(",",$colonnes)) { spip_log("SQLite : Erreur, impossible de creer un index sur plusieurs colonnes" ." sans qu'il ait de nom ($table, ($colonnes))", 'sqlite'); break; } else { $nom_index = $colonnes; } } // nom_index else { $nom_index = $colonnes = $colonne_origine ; } } spip_sqlite_create_index($nom_index, $table, $colonnes, $serveur); 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 : $query", 'sqlite'); return false; } break; // artillerie lourde pour sqlite2 ! } else { $def = trim(substr($do, 3)); $colonne_ajoutee = substr($def, 0, strpos($def,' ')); $def = substr($def, strlen($colonne_ajoutee)+1); if (!_sqlite_modifier_table($table, array($colonne_ajoutee), array('field'=>array($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; } // Fonction de creation d'une table SQL nommee $nom // http://doc.spip.org/@spip_sqlite_create function spip_sqlite_create($nom, $champs, $cles, $autoinc=false, $temporary=false, $serveur='',$requeter=true) { $query = _sqlite_requete_create($nom, $champs, $cles, $autoinc, $temporary, $ifnotexists=true, $serveur, $requeter); if (!$query) return false; $res = spip_sqlite_query($query, $serveur, $requeter); // SQLite ne cree pas les KEY sur les requetes CREATE TABLE // il faut donc les faire creer ensuite if (!$requeter) return $res; $ok = $res ? true : false; if ($ok) { foreach($cles as $k=>$v) { if (strpos($k, "KEY ") === 0) { $index = preg_replace("/KEY +/", '',$k); $ok &= $res = spip_sqlite_create_index($index, $nom, $v, $serveur); } } } return $ok ? true : false; } /** * Fonction pour creer une base de donnees SQLite * * @param string $nom le nom de la base (sans l'extension de fichier) * @param string $serveur le nom de la connexion * @param string $option options * * @return bool true si la base est creee. **/ function spip_sqlite_create_base($nom, $serveur='', $option=true) { $f = _DIR_DB . $nom . '.sqlite'; if (_sqlite_is_version(2, '', $serveur)) { $ok = sqlite_open($f, _SQLITE_CHMOD, $err); } else { $ok = new PDO("sqlite:$f"); } if ($ok) { unset($ok); return true; } unset($ok); return false; } // Fonction de creation d'une vue SQL nommee $nom // http://doc.spip.org/@spip_sqlite_create_view function spip_sqlite_create_view($nom, $query_select, $serveur='',$requeter=true) { if (!$query_select) return false; // vue deja presente if (sql_showtable($nom, false, $serveur)) { spip_log("Echec creation d'une vue sql ($nom) car celle-ci existe deja (serveur:$serveur)"); return false; } $query = "CREATE VIEW $nom AS ". $query_select; return spip_sqlite_query($query, $serveur, $requeter); } /** * Fonction de creation d'un INDEX * * @param string $nom : nom de l'index * @param string $table : table sql de l'index * @param string/array $champs : liste de champs sur lesquels s'applique l'index * @param string $serveur : nom de la connexion sql utilisee * @param bool $requeter : true pour executer la requete ou false pour retourner le texte de la requete * * @return bool ou requete */ function spip_sqlite_create_index($nom, $table, $champs, $serveur='', $requeter=true) { if (!($nom OR $table OR $champs)) { spip_log("Champ manquant pour creer un index sqlite ($nom, $table, (".join(',',$champs)."))"); return false; } // SQLite ne differentie pas noms des index en fonction des tables // il faut donc creer des noms uniques d'index pour une base sqlite $nom = $table.'_'.$nom; // enlever d'eventuelles parentheses deja presentes sur champs if (!is_array($champs)){ if ($champs[0]=="(") $champs = substr($champs,1,-1); $champs = array($champs); } $query = "CREATE INDEX $nom ON $table (" . join(',',$champs) . ")"; $res = spip_sqlite_query($query, $serveur, $requeter); if (!$requeter) return $res; if ($res) return true; else return false; } // en PDO/sqlite3, il faut calculer le count par une requete count(*) // pour les resultats de SELECT // cela est fait sans spip_sqlite_query() // http://doc.spip.org/@spip_sqlite_count function spip_sqlite_count($r, $serveur='',$requeter=true) { if (!$r) return 0; if (_sqlite_is_version(3, '', $serveur)){ // select ou autre (insert, update,...) ? if (isset($r->spipSqliteRowCount)) { // Ce compte est faux s'il y a des limit dans la requete :( // il retourne le nombre d'enregistrements sans le limit return $r->spipSqliteRowCount; } else { return $r->rowCount(); } } else { return sqlite_num_rows($r); } } // http://doc.spip.org/@spip_sqlite_countsel function spip_sqlite_countsel($from = array(), $where = array(), $groupby = '', $having = array(), $serveur='',$requeter=true) { $c = !$groupby ? '*' : ('DISTINCT ' . (is_string($groupby) ? $groupby : join(',', $groupby))); $r = spip_sqlite_select("COUNT($c)", $from, $where,'', '', '',$having, $serveur, $requeter); if ((is_resource($r) or is_object($r)) && $requeter) { // ressource : sqlite2, object : sqlite3 if (_sqlite_is_version(3,'',$serveur)){ list($n) = spip_sqlite_fetch($r, SPIP_SQLITE3_NUM, $serveur); } else { list($n) = spip_sqlite_fetch($r, SPIP_SQLITE2_NUM, $serveur); } spip_sqlite_free($r,$serveur); } return $n; } // http://doc.spip.org/@spip_sqlite_delete function spip_sqlite_delete($table, $where='', $serveur='',$requeter=true) { $res = spip_sqlite_query( _sqlite_calculer_expression('DELETE FROM', $table, ',') . _sqlite_calculer_expression('WHERE', $where), $serveur, $requeter); // renvoyer la requete inerte si demandee if (!$requeter) return $res; if ($res){ $link = _sqlite_link($serveur); if (_sqlite_is_version(3, $link)) { return $res->rowCount(); } else { return sqlite_changes($link); } } else return false; } // http://doc.spip.org/@spip_sqlite_drop_table function spip_sqlite_drop_table($table, $exist='', $serveur='',$requeter=true) { if ($exist) $exist =" IF EXISTS"; /* simuler le IF EXISTS - version 2 */ if ($exist && _sqlite_is_version(2, '', $serveur)){ $a = spip_sqlite_showtable($table, $serveur); if (!$a) return true; $exist = ''; } if (spip_sqlite_query("DROP TABLE$exist $table", $serveur, $requeter)) return true; else return false; } // supprime une vue // http://doc.spip.org/@spip_sqlite_drop_view function spip_sqlite_drop_view($view, $exist='', $serveur='',$requeter=true) { if ($exist) $exist =" IF EXISTS"; /* simuler le IF EXISTS - version 2 */ if ($exist && _sqlite_is_version(2, '', $serveur)){ $a = spip_sqlite_showtable($view, $serveur); if (!$a) return true; $exist = ''; } return spip_sqlite_query("DROP VIEW$exist $view", $serveur, $requeter); } /** * Fonction de suppression d'un INDEX * * @param string $nom : nom de l'index * @param string $table : table sql de l'index * @param string $serveur : nom de la connexion sql utilisee * @param bool $requeter : true pour executer la requete ou false pour retourner le texte de la requete * * @return bool ou requete */ function spip_sqlite_drop_index($nom, $table, $serveur='', $requeter=true) { if (!($nom OR $table)) { spip_log("Champ manquant pour supprimer un index sqlite ($nom, $table)"); return false; } // SQLite ne differentie pas noms des index en fonction des tables // il faut donc creer des noms uniques d'index pour une base sqlite $index = $table.'_'.$nom; $exist =" IF EXISTS"; /* simuler le IF EXISTS - version 2 */ if (_sqlite_is_version(2, '', $serveur)){ $a = spip_sqlite_showtable($table, $serveur); if (!isset($a['key']['KEY '.$nom])) return true; $exist = ''; } $query = "DROP INDEX$exist $index"; return spip_sqlite_query($query, $serveur, $requeter); } /** * Retourne la derniere erreur generee * * @param $serveur nom de la connexion * @return string erreur eventuelle **/ // http://doc.spip.org/@spip_sqlite_error function spip_sqlite_error($query='', $serveur='') { $link = _sqlite_link($serveur); if (_sqlite_is_version(3, $link)) { $errs = $link->errorInfo(); $s = ''; foreach($errs as $n=>$e){ $s .= "\n$n : $e"; } } elseif ($link) { $s = sqlite_error_string(sqlite_last_error($link)); } else { $s = ": aucune ressource sqlite (link)"; } if ($s) spip_log("$s - $query", 'sqlite'); return $s; } /** * Retourne le numero de la derniere erreur SQL * (sauf que SQLite semble ne connaitre que 0 ou 1) * * @param $serveur nom de la connexion * @return int 0 pas d'erreur / 1 une erreur **/ // http://doc.spip.org/@spip_sqlite_errno function spip_sqlite_errno($serveur='') { $link = _sqlite_link($serveur); if (_sqlite_is_version(3, $link)){ $t = $link->errorInfo(); $s = $t[1]; } elseif ($link) { $s = sqlite_last_error($link); } else { $s = ": aucune ressource sqlite (link)"; } if ($s) spip_log("Erreur sqlite $s"); return $s ? 1 : 0; } // http://doc.spip.org/@spip_sqlite_explain function spip_sqlite_explain($query, $serveur='',$requeter=true){ if (strpos(ltrim($query), 'SELECT') !== 0) return array(); $requete = new sqlite_traiter_requete("$query", $serveur); $requete->traduire_requete(); // mysql -> sqlite $requete->query = 'EXPLAIN ' . $requete->query; if (!$requeter) return $requete; // on ne trace pas ces requetes, sinon on obtient un tracage sans fin... $requete->tracer = false; $r = $requete->executer_requete(); return $r ? spip_sqlite_fetch($r, null, $serveur) : false; // hum ? etrange ca... a verifier } // http://doc.spip.org/@spip_sqlite_fetch function spip_sqlite_fetch($r, $t='', $serveur='',$requeter=true) { $link = _sqlite_link($serveur); if (!$t) { if (_sqlite_is_version(3, $link)) { $t = SPIP_SQLITE3_ASSOC; } else { $t = SPIP_SQLITE2_ASSOC; } } if (_sqlite_is_version(3, $link)){ if ($r) $retour = $r->fetch($t); } elseif ($r) { $retour = sqlite_fetch_array($r, $t); } // les version 2 et 3 parfois renvoie des 'table.titre' au lieu de 'titre' tout court ! pff ! // suppression de 'table.' pour toutes les cles (c'est un peu violent !) if ($retour){ $new = array(); foreach ($retour as $cle=>$val){ if (($pos = strpos($cle, '.'))!==false){ $cle = substr($cle,++$pos); } $new[$cle] = $val; } $retour = &$new; } return $retour; } function spip_sqlite_seek($r, $row_number, $serveur='',$requeter=true) { if ($r){ $link = _sqlite_link($serveur); if (_sqlite_is_version(3, $link)){ // encore un truc de bien fichu : PDO ne PEUT PAS faire de seek ou de rewind... // je me demande si pour sqlite 3 il ne faudrait pas mieux utiliser // les nouvelles fonctions sqlite3_xx (mais encore moins presentes...) return false; } else { return sqlite_seek($r, $row_number); } } } // http://doc.spip.org/@spip_sqlite_free function spip_sqlite_free(&$r, $serveur='',$requeter=true) { unset($r); return true; //return sqlite_free_result($r); } // http://doc.spip.org/@spip_sqlite_get_charset function spip_sqlite_get_charset($charset=array(), $serveur='',$requeter=true){ //$c = !$charset ? '' : (" LIKE "._q($charset['charset'])); //return spip_sqlite_fetch(sqlite_query(_sqlite_link($serveur), "SHOW CHARACTER SET$c"), NULL, $serveur); } // http://doc.spip.org/@spip_sqlite_hex function spip_sqlite_hex($v){ return hexdec($v); } // http://doc.spip.org/@spip_sqlite_in function spip_sqlite_in($val, $valeurs, $not='', $serveur='',$requeter=true) { $n = $i = 0; $in_sql =""; while ($n = strpos($valeurs, ',', $n+1)) { if ((++$i) >= 255) { $in_sql .= "($val $not IN (" . substr($valeurs, 0, $n) . "))\n" . ($not ? "AND\t" : "OR\t"); $valeurs = substr($valeurs, $n+1); $i = $n = 0; } } $in_sql .= "($val $not IN ($valeurs))"; return "($in_sql)"; } // http://doc.spip.org/@spip_sqlite_insert function spip_sqlite_insert($table, $champs, $valeurs, $desc='', $serveur='',$requeter=true) { $connexion = $GLOBALS['connexions'][$serveur ? $serveur : 0]; $prefixe = $connexion['prefixe']; $sqlite = $connexion['link']; $db = $connexion['db']; if ($prefixe) $table = preg_replace('/^spip/', $prefixe, $table); if (isset($_GET['var_profile'])) { include_spip('public/tracer'); $t = trace_query_start(); } else $t = 0 ; $query="INSERT INTO $table ".($champs?"$champs VALUES $valeurs":"DEFAULT VALUES"); if ($r = spip_sqlite_query($query, $serveur, $requeter)) { if (!$requeter) return $r; if (_sqlite_is_version(3, $sqlite)) $nb = $sqlite->lastInsertId(); else $nb = sqlite_last_insert_rowid($sqlite); } else $nb = 0; $err = spip_sqlite_error($query, $serveur); return $t ? trace_query_end($query, $t, $nb, $err, $serveur) : $nb; } // http://doc.spip.org/@spip_sqlite_insertq function spip_sqlite_insertq($table, $couples=array(), $desc=array(), $serveur='',$requeter=true) { if (!$desc) $desc = description_table($table); if (!$desc) die("$table insertion sans description"); $fields = isset($desc['field'])?$desc['field']:array(); foreach ($couples as $champ => $val) { $couples[$champ]= _sqlite_calculer_cite($val, $fields[$champ]); } // recherche de champs 'timestamp' pour mise a jour auto de ceux-ci $couples = _sqlite_ajouter_champs_timestamp($table, $couples, $desc, $serveur); // si aucun champ donne pour l'insertion, on en cherche un avec un DEFAULT // sinon sqlite3 ne veut pas inserer $cles = $valeurs = ""; if (count($couples)) { $cles = "(".join(',',array_keys($couples)).")"; $valeurs = "(".join(',', $couples).")"; } return spip_sqlite_insert($table, $cles , $valeurs , $desc, $serveur, $requeter); } // http://doc.spip.org/@spip_sqlite_insertq_multi function spip_sqlite_insertq_multi($table, $tab_couples=array(), $desc=array(), $serveur='',$requeter=true) { foreach ($tab_couples as $couples) { $retour = spip_sqlite_insertq($table, $couples, $desc, $serveur, $requeter); } // renvoie le dernier id d'autoincrement ajoute return $retour; } // http://doc.spip.org/@spip_sqlite_listdbs function spip_sqlite_listdbs($serveur='',$requeter=true) { _sqlite_init(); if (!is_dir($d = substr(_DIR_DB,0,-1))){ return array(); } include_spip('inc/flock'); $bases = preg_files($d, $pattern = '(.*)\.sqlite$'); $bds = array(); foreach($bases as $b){ // pas de bases commencant pas sqlite // (on s'en sert pour l'installation pour simuler la presence d'un serveur) // les bases sont de la forme _sqliteX_tmp_spip_install.sqlite if (strpos($b, '_sqlite')) continue; $bds[] = preg_replace(";.*/$pattern;iS",'$1', $b); } return $bds; } // http://doc.spip.org/@spip_sqlite_multi function spip_sqlite_multi ($objet, $lang) { $r = "PREG_REPLACE(" . $objet . ",'.*[\[]" . $lang . "[\]]([^\[]*).*', '$1') AS multi"; return $r; } /** * Optimise une table SQL * Note: Sqlite optimise TOUTE un fichier sinon rien. * On evite donc 2 traitements sur la meme base dans un hit. * * @param $table nom de la table a optimiser * @param $serveur nom de la connexion * @param $requeter effectuer la requete ? sinon retourner son code * @return bool|string true / false / requete **/ // http://doc.spip.org/@spip_sqlite_optimize function spip_sqlite_optimize($table, $serveur='', $requeter=true) { static $do = false; if ($requeter and $do) {return true;} if ($requeter) { $do = true; } return spip_sqlite_query("VACUUM", $serveur, $requeter); } // avoir le meme comportement que _q() function spip_sqlite_quote($v, $type=''){ if (is_array($v)) return join(",", array_map('spip_sqlite_quote', $v)); if (is_int($v)) return strval($v); if (strncmp($v,'0x',2)==0 AND ctype_xdigit(substr($v,2))) return hexdec(substr($v,2)); if ($type === 'int' AND !$v) return '0'; if (function_exists('sqlite_escape_string')) { return "'" . sqlite_escape_string($v) . "'"; } // trouver un link sqlite3 pour faire l'echappement foreach ($GLOBALS['connexions'] as $s) { if (_sqlite_is_version(3, $l = $s['link'])){ return $l->quote($v); } } } /** * Tester si une date est proche de la valeur d'un champ * * @param string $champ le nom du champ a tester * @param int $interval valeur de l'interval : -1, 4, ... * @param string $unite utite utilisee (DAY, MONTH, YEAR, ...) * @return string expression SQL **/ function spip_sqlite_date_proche($champ, $interval, $unite) { $op = $interval > 0 ? '>' : '<'; return "($champ $op datetime('" . date("Y-m-d H:i:s") . "', '$interval $unite'))"; } // http://doc.spip.org/@spip_sqlite_replace function spip_sqlite_replace($table, $couples, $desc=array(), $serveur='',$requeter=true) { if (!$desc) $desc = description_table($table); if (!$desc) die("$table insertion sans description"); $fields = isset($desc['field'])?$desc['field']:array(); foreach ($couples as $champ => $val) { $couples[$champ]= _sqlite_calculer_cite($val, $fields[$champ]); } // recherche de champs 'timestamp' pour mise a jour auto de ceux-ci $couples = _sqlite_ajouter_champs_timestamp($table, $couples, $desc, $serveur); return spip_sqlite_query("REPLACE INTO $table (" . join(',',array_keys($couples)) . ') VALUES (' .join(',',$couples) . ')', $serveur); } // http://doc.spip.org/@spip_sqlite_replace_multi function spip_sqlite_replace_multi($table, $tab_couples, $desc=array(), $serveur='',$requeter=true) { // boucler pour trainter chaque requete independemment foreach ($tab_couples as $couples){ $retour = spip_sqlite_replace($table, $couples, $desc, $serveur,$requeter); } // renvoie le dernier id return $retour; } // http://doc.spip.org/@spip_sqlite_select function spip_sqlite_select($select, $from, $where='', $groupby='', $orderby='', $limit='', $having='', $serveur='',$requeter=true) { // version() n'est pas connu de sqlite $select = str_replace('version()', 'sqlite_version()',$select); // recomposer from $from = (!is_array($from) ? $from : _sqlite_calculer_select_as($from)); $query = _sqlite_calculer_expression('SELECT', $select, ', ') . _sqlite_calculer_expression('FROM', $from, ', ') . _sqlite_calculer_expression('WHERE', $where) . _sqlite_calculer_expression('GROUP BY', $groupby, ',') . _sqlite_calculer_expression('HAVING', $having) . ($orderby ? ("\nORDER BY " . _sqlite_calculer_order($orderby)) :'') . ($limit ? "\nLIMIT $limit" : ''); return spip_sqlite_query($query, $serveur, $requeter); } // http://doc.spip.org/@spip_sqlite_selectdb function spip_sqlite_selectdb($db, $serveur='',$requeter=true) { _sqlite_init(); // interdire la creation d'une nouvelle base, // sauf si on est dans l'installation if (!is_file($f = _DIR_DB . $db . '.sqlite') && (!defined('_ECRIRE_INSTALL') || !_ECRIRE_INSTALL)) return false; // se connecter a la base indiquee // avec les identifiants connus $index = $serveur ? $serveur : 0; if ($link = spip_connect_db('', '', '', '', '@selectdb@' . $db , $serveur, '', '')){ if (($db==$link['db']) && $GLOBALS['connexions'][$index] = $link) return $db; } else { spip_log("Impossible de selectionner la base $db", 'sqlite'); return false; } } // http://doc.spip.org/@spip_sqlite_set_charset function spip_sqlite_set_charset($charset, $serveur='',$requeter=true){ #spip_log("changement de charset sql : "."SET NAMES "._q($charset)); # return spip_sqlite_query("SET NAMES ". spip_sqlite_quote($charset), $serveur); //<-- Passe pas ! } // http://doc.spip.org/@spip_sqlite_showbase function spip_sqlite_showbase($match, $serveur='',$requeter=true){ // type est le type d'entrée : table / index / view // on ne retourne que les tables (?) et non les vues... # ESCAPE non supporte par les versions sqlite <3 # return spip_sqlite_query("SELECT name FROM sqlite_master WHERE type='table' AND tbl_name LIKE "._q($match)." ESCAPE '\'", $serveur, $requeter); $match = preg_quote($match); $match = str_replace("\\\_","[[TIRETBAS]]",$match); $match = str_replace("\\\%","[[POURCENT]]",$match); $match = str_replace("_",".",$match); $match = str_replace("%",".*",$match); $match = str_replace("[[TIRETBAS]]","_",$match); $match = str_replace("[[POURCENT]]","%",$match); $match = "^$match$"; return spip_sqlite_query("SELECT name FROM sqlite_master WHERE type='table' AND tbl_name REGEXP "._q($match), $serveur, $requeter); } // http://doc.spip.org/@spip_sqlite_showtable function spip_sqlite_showtable($nom_table, $serveur='',$requeter=true){ $query = 'SELECT sql, type FROM' . ' (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_%'" . ' ORDER BY substr(type,2,1), name'; $a = spip_sqlite_query($query, $serveur, $requeter); if (!$a) return ""; if (!$requeter) return $a; if (!($a = spip_sqlite_fetch($a, null, $serveur))) return ""; $vue = ($a['type'] == 'view'); // table | vue // c'est une table // il faut parser le create if (!$vue) { if (!preg_match("/^[^(),]*\((([^()]*(\([^()]*\))?[^()]*)*)\)[^()]*$/", array_shift($a), $r)) return ""; else { $dec = $r[1]; if (preg_match("/^(.*?),([^,]*KEY.*)$/s", $dec, $r)) { $namedkeys = $r[2]; $dec = $r[1]; } else $namedkeys = ""; $fields = array(); foreach (explode(",",$dec) as $v) { preg_match("/^\s*([^\s]+)\s+(.*)/",$v,$r); // trim car 'Sqlite Manager' (plugin Firefox) utilise des guillemets // lorsqu'on modifie une table avec cet outil. // possible que d'autres fassent de meme. $fields[ trim(strtolower($r[1]),'"') ] = $r[2]; } // 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; } } // 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)) { $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); } // 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), $serveur, $requeter); } // 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), $serveur, $requeter); } /* * * Ensuite les fonctions non abstraites * crees pour l'occasion de sqlite * */ // fonction pour la premiere connexion a un serveur SQLite // 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 // 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 ?) // 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()) // 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)" // 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) // http://doc.spip.org/@_sqlite_calculer_order function _sqlite_calculer_order($orderby) { return (is_array($orderby)) ? join(", ", $orderby) : $orderby; } // renvoie des 'nom AS alias' // 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 // 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 */ // 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 * 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 * 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 * */ // http://doc.spip.org/@_sqlite_modifier_table function _sqlite_modifier_table($table, $colonne, $opt=array(), $serveur=''){ if (is_array($table)) { reset($table); list($table_origine,$table_destination) = each($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)) { reset($colonne); list($colonne_origine,$colonne_destination) = each($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); $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){ if ($colonne_origine && ($c == $colonne_origine)) { // si pas DROP if ($colonne_destination){ $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){ $fields[$colonne_destination] = $opt['field'][$colonne_destination]; } // key... $keys = array(); foreach ($def_origine['key'] as $c=>$d){ $c = str_replace($colonne_origine,$colonne_destination,$c); $d = str_replace($colonne_origine,$colonne_destination,$d); // seulement si on ne supprime pas la colonne ! if ($d) $keys[$c] = $d; } // 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"; } } // 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 */ // 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', 'seek' => 'spip_sqlite_seek', '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 // 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 // http://doc.spip.org/@_sqlite_remplacements_definitions_table function _sqlite_remplacements_definitions_table($query,$autoinc=false){ // quelques remplacements $num = "(\s*\([0-9]*\))?"; $enum = "(\s*\([^\)]*\))?"; $remplace = array( '/enum'.$enum.'/is' => 'VARCHAR', '/binary/is' => '', '/COLLATE \w+_bin/is' => '', '/auto_increment/is' => '', '/(timestamp .* )ON .*$/is' => '\\1', '/character set \w+/is' => '', '/((big|small|medium|tiny)?int(eger)?)'.$num.'\s*unsigned/is' => '\\1 UNSIGNED', '/(text\s+not\s+null)\s*$/is' => "\\1 DEFAULT ''", ); // 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() */ // 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; // 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; } } } 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 = ","; } $ifnotexists = ""; if ($_ifnotexists) { // simuler le IF NOT EXISTS - version 2 if (_sqlite_is_version(2, '', $serveur)){ $a = spip_sqlite_showtable($nom, $serveur); if ($a) return false; } // sinon l'ajouter en version 3 else { $ifnotexists = ' IF NOT EXISTS'; } } $temporary = $temporary ? ' TEMPORARY':''; $q = "CREATE$temporary TABLE$ifnotexists $nom ($query" . ($keys ? ",$keys" : '') . ")\n"; return $q; } /* * 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 */ // 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){ $f = charger_fonction('trouver_table', 'base'); $desc = $f($table, $serveur); // si pas de description, on ne fait rien, ou on die() ? if (!$desc OR !$desc['field']) 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) $tables[$table][] = $k; } } // 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 */ // 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é // constructeur // http://doc.spip.org/@sqlite_traiter_requete function sqlite_traiter_requete($query, $serveur = ''){ $this->query = $query; $this->serveur = strtolower($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 // http://doc.spip.org/@executer_requete function executer_requete(){ $err = ""; if ($this->tracer) { include_spip('public/tracer'); $t = trace_query_start(); } else $t = 0 ; # spip_log("requete: $this->serveur >> $this->query",'query'); // boum ? pourquoi ? if ($this->link){ // 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'){ if ($r) { $l = $this->link->query($this->query); $r->spipSqliteRowCount = count($l->fetchAll()); unset($l); } 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'); } else $err=""; } else { $r = false; } if (spip_sqlite_errno($serveur)) $err .= spip_sqlite_error($this->query, $serveur); return $t ? trace_query_end($this->query, $t, $r, $err, $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 // http://doc.spip.org/@traduire_requete function traduire_requete(){ // // 1) Protection des textes en les remplacant par des codes // // enlever les 'textes' et initialiser avec list($this->query, $textes) = query_echappe_textes($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); } // Correction Using // 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)) { 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; // 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; } } // 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 // // Correction Antiquotes et echappements // ` => rien if (strpos($this->query,'`')!==false) $this->query = str_replace('`','', $this->query); $this->query = query_reinjecte_textes($this->query, $textes); } // les callbacks // remplacer DATE_ / INTERVAL par DATE...strtotime // 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 // 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 // http://doc.spip.org/@_remplacerTexteParCode function _remplacerTexteParCode($matches){ $this->textes[$code = "%@##".count($this->textes)."##@%"] = $matches[1]; return $code; } } ?>