// base temporaire tant qu'on ne connait pas son vrai nom // pour tester la connexion $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); } } 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){ $e = sqlite_last_error($db); spip_log("Impossible d'ouvrir la base SQLite($sqlite_version) $f : $e", 'sqlite.'._LOG_HS); return false; } if ($link){ $last_connect = array( 'addr' => $addr, 'port' => $port, 'login' => $login, 'pass' => $pass, 'db' => $db, 'prefixe' => $prefixe, ); // etre sur qu'on definit bien les fonctions a chaque nouvelle connexion include_spip('req/sqlite_fonctions'); _sqlite_init_functions($link); } return array( 'db' => $db, 'prefixe' => $prefixe ? $prefixe : $db, 'link' => $link, ); } /** * Fonction de requete generale, munie d'une trace a la demande * * @param string $query * Requete a executer * @param string $serveur * Nom du connecteur * @param bool $requeter * Effectuer la requete ? * - true pour executer * - false pour retourner le texte de la requete * @return bool|SQLiteResult|string * Resultat de la requete */ function spip_sqlite_query($query, $serveur = '', $requeter = true){ #spip_log("spip_sqlite_query() > $query",'sqlite.'._LOG_DEBUG); #_sqlite_init(); // fait la premiere fois dans spip_sqlite $query = spip_sqlite::traduire_requete($query, $serveur); if (!$requeter) return $query; return spip_sqlite::executer_requete($query, $serveur); } /* 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); // traduire la requete pour recuperer les bons noms de table $query = spip_sqlite::traduire_requete($query, $serveur); /* * 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.'._LOG_ERREUR); 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 KEY|DROP INDEX|DROP COLUMN|DROP' .'|CHANGE COLUMN|CHANGE|MODIFY|RENAME TO|RENAME' .'|ADD PRIMARY KEY|ADD KEY|ADD INDEX|ADD UNIQUE KEY|ADD UNIQUE' .'|ADD COLUMN|ADD' .')\s*([^\s]*)\s*(.*)?/i', $do, $matches)){ spip_log("SQLite : Probleme de ALTER TABLE, utilisation non reconnue dans : $do \n(requete d'origine : $query)", 'sqlite.'._LOG_ERREUR); 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)); $defo = $defr; // garder la def d'origine pour certains cas // 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 KEY': 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 // on reprend la def d'origine car _sqlite_modifier_table va refaire la translation // en tenant compte de la cle primaire (ce qui est mieux) $def = trim($defo); $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': // on reprend la def d'origine car _sqlite_modifier_table va refaire la translation // en tenant compte de la cle primaire (ce qui est mieux) if (!_sqlite_modifier_table( $table, $colonne_origine, array('field' => array($colonne_origine => $defo)), $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)){ if (!spip_sqlite::executer_requete("$debut $do", $serveur)){ spip_log("SQLite : Erreur ALTER TABLE / RENAME : $query", 'sqlite.'._LOG_ERREUR); 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.'._LOG_ERREUR); 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 UNIQUE KEY': case 'ADD UNIQUE': $unique=true; case 'ADD INDEX': case 'ADD KEY': // 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(_LOG_GRAVITE_ERREUR, "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, $unique, $serveur); break; // pas geres en sqlite2 case 'ADD COLUMN': $do = "ADD".substr($do, 10); case 'ADD': default: if (_sqlite_is_version(3, '', $serveur) AND !preg_match(',primary\s+key,i',$do)){ if (!spip_sqlite::executer_requete("$debut $do", $serveur)){ spip_log("SQLite : Erreur ALTER TABLE / ADD : $query", 'sqlite.'._LOG_ERREUR); return false; } break; } // artillerie lourde pour sqlite2 ! // ou si la colonne est aussi primary key // cas du add id_truc int primary key // ajout d'une colonne qui passe en primary key directe else { $def = trim(substr($do, 3)); $colonne_ajoutee = substr($def, 0, strpos($def, ' ')); $def = substr($def, strlen($colonne_ajoutee)+1); $opts = array(); if (preg_match(',primary\s+key,i',$def)){ $opts['key'] = array('PRIMARY KEY' => $colonne_ajoutee); $def = preg_replace(',primary\s+key,i','',$def); } $opts['field'] = array($colonne_ajoutee => $def); if (!_sqlite_modifier_table($table, array($colonne_ajoutee), $opts, $serveur)){ spip_log("SQLite : Erreur ALTER TABLE / ADD : $query", 'sqlite.'._LOG_ERREUR); return false; } } break; } // tout est bon, ouf ! spip_log("SQLite ($serveur) : Changements OK : $debut $do", 'sqlite.'._LOG_INFO); } spip_log("SQLite ($serveur) : fin ALTER TABLE OK !", 'sqlite.'._LOG_INFO); return true; } /** * Fonction de creation d'une table SQL nommee $nom * http://doc.spip.org/@spip_sqlite_create * * @param string $nom * @param array $champs * @param array $cles * @param bool $autoinc * @param bool $temporary * @param string $serveur * @param bool $requeter * @return bool|SQLiteResult|string */ 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 (preg_match(',^(KEY|UNIQUE)\s,i',$k,$m)){ $index = trim(substr($k,strlen($m[1]))); $unique = (strlen($m[1])>3); $ok &= spip_sqlite_create_index($index, $nom, $v, $unique, $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 = $nom.'.sqlite'; if (strpos($nom, "/")===false) $f = _DIR_DB.$f; 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 * * @param string $nom * Nom de la vue a creer * @param string $query_select * Texte de la requete de selection servant de base a la vue * @param string $serveur * Nom du connecteur * @param bool $requeter * Effectuer la requete ? * - true pour executer * - false pour retourner le texte de la requete * @return bool|SQLiteResult|string * Resultat de la requete ou * - false si erreur ou si la vue existe deja * - string texte de la requete si $requeter vaut false */ 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)", 'sqlite.'._LOG_ERREUR); 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, $unique='', $serveur = '', $requeter = true){ if (!($nom OR $table OR $champs)){ spip_log("Champ manquant pour creer un index sqlite ($nom, $table, (".join(',', $champs)."))", 'sqlite.'._LOG_ERREUR); 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); // supprimer l'info de longueur d'index mysql en fin de champ $champs = preg_replace(",\(\d+\)$,","",$champs); } $ifnotexists = ""; $version = spip_sqlite_fetch(spip_sqlite_query("select sqlite_version() AS sqlite_version",$serveur),'',$serveur); if (!function_exists('spip_version_compare')) include_spip('plugins/installer'); if ($version AND spip_version_compare($version['sqlite_version'],'3.3.0','>=')) { $ifnotexists = ' IF NOT EXISTS'; } else { /* simuler le IF EXISTS - version 2 et sqlite < 3.3a */ $a = spip_sqlite_showtable($table, $serveur); if (isset($a['key']['KEY '.$nom])) return true; } $query = "CREATE ".($unique?"UNIQUE ":"")."INDEX$ifnotexists $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 * * @param $r * @param string $serveur * @param bool $requeter * @return int */ function spip_sqlite_count($r, $serveur = '', $requeter = true){ if (!$r) return 0; if (_sqlite_is_version(3, '', $serveur)){ // select ou autre (insert, update,...) ? // (link,requete) a compter if (is_array($r->spipSqliteRowCount)){ list($link,$query) = $r->spipSqliteRowCount; // amelioration possible a tester intensivement : pas de order by pour compter ! // $query = preg_replace(",ORDER BY .+(LIMIT\s|HAVING\s|GROUP BY\s|$),Uims","\\1",$query); $query = "SELECT count(*) as zzzzsqlitecount FROM ($query)"; $l = $link->query($query); $i = 0; if ($l AND $z = $l->fetch()) $i = $z['zzzzsqlitecount']; $r->spipSqliteRowCount = $i; } 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($r) = spip_sqlite_fetch($r, SPIP_SQLITE3_NUM, $serveur); } else { list($r) = spip_sqlite_fetch($r, SPIP_SQLITE2_NUM, $serveur); } } return $r; } // 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 * * @param $view * @param string $exist * @param string $serveur * @param bool $requeter * @return bool|SQLiteResult|string */ 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)", 'sqlite.'._LOG_ERREUR); 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 dernière erreur generée * * @param $serveur * nom de la connexion * @return string * erreur eventuelle **/ function spip_sqlite_error($query = '', $serveur = ''){ $link = _sqlite_link($serveur); if (_sqlite_is_version(3, $link)){ $errs = $link->errorInfo(); /* $errs[0] numero SQLState ('HY000' souvent lors d'une erreur) http://www.easysoft.com/developer/interfaces/odbc/sqlstate_status_return_codes.html $errs[1] numéro d'erreur SQLite (souvent 1 lors d'une erreur) http://www.sqlite.org/c3ref/c_abort.html $errs[2] Le texte du message d'erreur */ $s = ''; if (ltrim($errs[0],'0')) { // 00000 si pas d'erreur $s = "$errs[2]"; } } elseif ($link) { $s = sqlite_error_string(sqlite_last_error($link)); } else { $s = ": aucune ressource sqlite (link)"; } if ($s) spip_log("$s - $query", 'sqlite.'._LOG_ERREUR); return $s; } /** * Retourne le numero de la dernière erreur SQL * * Le numéro (en sqlite3/pdo) est un retour ODBC tel que (très souvent) HY000 * http://www.easysoft.com/developer/interfaces/odbc/sqlstate_status_return_codes.html * * @param string $serveur * nom de la connexion * @return int|string * 0 pas d'erreur * 1 ou autre erreur (en sqlite 2) * 'HY000/1' : numéro de l'erreur SQLState / numéro d'erreur interne SQLite (en sqlite 3) **/ function spip_sqlite_errno($serveur = ''){ $link = _sqlite_link($serveur); if (_sqlite_is_version(3, $link)){ $t = $link->errorInfo(); $s = ltrim($t[0],'0'); // 00000 si pas d'erreur if ($s) $s .= ' / ' . $t[1]; // ajoute l'erreur du moteur SQLite } elseif ($link) { $s = sqlite_last_error($link); } else { $s = ": aucune ressource sqlite (link)"; } if ($s) spip_log("Erreur sqlite $s", 'sqlite.'._LOG_ERREUR); return $s ? $s : 0; } // http://doc.spip.org/@spip_sqlite_explain function spip_sqlite_explain($query, $serveur = '', $requeter = true){ if (strpos(ltrim($query), 'SELECT')!==0) return array(); $query = spip_sqlite::traduire_requete($query, $serveur); $query = 'EXPLAIN '.$query; if (!$requeter) return $query; // on ne trace pas ces requetes, sinon on obtient un tracage sans fin... $r = spip_sqlite::executer_requete($query, $serveur, false); 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); $is_v3 = _sqlite_is_version(3, $link); if (!$t) $t = ($is_v3 ? SPIP_SQLITE3_ASSOC : SPIP_SQLITE2_ASSOC); $retour = false; if ($r) $retour = ($is_v3 ? $r->fetch($t) : 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 !) // c'est couteux : on ne verifie que la premiere ligne pour voir si on le fait ou non if ($retour AND strpos(implode('',array_keys($retour)),'.')!==false){ foreach ($retour as $cle => $val){ if (($pos = strpos($cle, '.'))!==false){ $retour[substr($cle, $pos+1)] = &$retour[$cle]; unset($retour[$cle]); } } } 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){ $query = "INSERT INTO $table ".($champs ? "$champs VALUES $valeurs" : "DEFAULT VALUES"); if ($r = spip_sqlite_query($query, $serveur, $requeter)){ if (!$requeter) return $r; $nb = spip_sqlite::last_insert_id($serveur); } else $nb = 0; $err = spip_sqlite_error($query, $serveur); // cas particulier : ne pas substituer la reponse spip_sqlite_query si on est en profilage return isset($_GET['var_profile']) ? $r : $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, $serveur); 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); $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){ if (!$desc) $desc = description_table($table, $serveur); if (!$desc) die("$table insertion sans description"); if (!isset($desc['field'])) $desc['field'] = array(); // recuperer les champs 'timestamp' pour mise a jour auto de ceux-ci $maj = _sqlite_ajouter_champs_timestamp($table, array(), $desc, $serveur); // seul le nom de la table est a traduire ici : // le faire une seule fois au debut $query_start = "INSERT INTO $table "; $query_start = spip_sqlite::traduire_requete($query_start,$serveur); // ouvrir une transaction if ($requeter) spip_sqlite::demarrer_transaction($serveur); while ($couples = array_shift($tab_couples)){ foreach ($couples as $champ => $val){ $couples[$champ] = _sqlite_calculer_cite($val, $desc['field'][$champ]); } // inserer les champs timestamp par defaut $couples = array_merge($maj,$couples); $champs = $valeurs = ""; if (count($couples)){ $champs = "(".join(',', array_keys($couples)).")"; $valeurs = "(".join(',', $couples).")"; $query = $query_start."$champs VALUES $valeurs"; } else $query = $query_start."DEFAULT VALUES"; if ($requeter) $retour = spip_sqlite::executer_requete($query,$serveur); // sur le dernier couple uniquement if (!count($tab_couples)){ $nb = 0; if ($requeter) $nb = spip_sqlite::last_insert_id($serveur); else return $query; } $err = spip_sqlite_error($query, $serveur); } if ($requeter) spip_sqlite::finir_transaction($serveur); // renvoie le dernier id d'autoincrement ajoute // cas particulier : ne pas substituer la reponse spip_sqlite_query si on est en profilage return isset($_GET['var_profile']) ? $retour : $nb; } /** * Retourne si le moteur SQL prefere utiliser des transactions. * * @param * @return bool true / false **/ function spip_sqlite_preferer_transaction($serveur = '', $requeter = true) { return true; } /** * Demarre une transaction. * Pratique pour des sql_updateq() dans un foreach, * parfois 100* plus rapide s'ils sont nombreux en sqlite ! * **/ function spip_sqlite_demarrer_transaction($serveur = '', $requeter = true) { if (!$requeter) return "BEGIN TRANSACTION"; spip_sqlite::demarrer_transaction($serveur); return true; } /** * Cloture une transaction. * **/ function spip_sqlite_terminer_transaction($serveur = '', $requeter = true) { if (!$requeter) return "COMMIT"; spip_sqlite::finir_transaction($serveur); return true; } // 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 = "EXTRAIRE_MULTI(" . $objet . ", '" . $lang . "') 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); } // /** * echapper une valeur selon son type ou au mieux * comme le fait _q() mais pour sqlite avec ses specificites * * @param string|array|number $v * @param string $type * @return string|number */ function spip_sqlite_quote($v, $type = ''){ if (!is_array($v)) return _sqlite_calculer_cite($v,$type); // si c'est un tableau, le parcourir en propageant le type foreach($v as $k=>$r) $v[$k] = spip_sqlite_quote($r, $type); return join(",", $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, $serveur); 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" : ''); // dans un select, on doit renvoyer la requête en cas d'erreur $res = spip_sqlite_query($query, $serveur, $requeter); // texte de la requete demande ? if (!$requeter) return $res; // erreur survenue ? if ($res === false) { return spip_sqlite::traduire_requete($query, $serveur); } return $res; } /** * Selectionne un fichier de base de donnees * * @param string $nom * Nom de la base a utiliser * @param string $serveur * Nom du connecteur * @param bool $requeter * Inutilise * * @return bool|string * Nom de la base en cas de success. * False en cas d'erreur. **/ 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)){ spip_log("Il est interdit de creer la base $db", 'sqlite.'._LOG_HS); 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.'._LOG_HS); return false; } } // http://doc.spip.org/@spip_sqlite_set_charset function spip_sqlite_set_charset($charset, $serveur = '', $requeter = true){ # spip_log("Gestion charset sql a ecrire : "."SET NAMES "._q($charset), 'sqlite.'._LOG_ERREUR); # return spip_sqlite_query("SET NAMES ". spip_sqlite_quote($charset), $serveur); //<-- Passe pas ! } /** * Retourne une ressource de la liste des tables de la base de données * * @param string $match * Filtre sur tables à récupérer * @param string $serveur * Connecteur de la base * @param bool $requeter * true pour éxecuter la requête * false pour retourner le texte de la requête. * @return ressource * Ressource à utiliser avec sql_fetch() **/ 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); } define('_SQLITE_RE_SHOW_TABLE', '/^[^(),]*\(((?:[^()]*\((?:[^()]*\([^()]*\))?[^()]*\)[^()]*)*[^()]*)\)[^()]*$/'); // 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(_SQLITE_RE_SHOW_TABLE, array_shift($a), $r)){ return ""; } else { $desc = $r[1]; // extraction d'une KEY éventuelle en prenant garde de ne pas // relever un champ dont le nom contient KEY (ex. ID_WHISKEY) if (preg_match("/^(.*?),([^,]*\sKEY[ (].*)$/s", $desc, $r)){ $namedkeys = $r[2]; $desc = $r[1]; } else $namedkeys = ""; $fields = array(); $keys = array(); // enlever les contenus des valeurs DEFAULT 'xxx' qui pourraient perturber // par exemple s'il contiennent une virgule. // /!\ cela peut aussi echapper le nom des champs si la table a eu des operations avec SQLite Manager ! list($desc, $echaps) = query_echappe_textes($desc); // separer toutes les descriptions de champs, separes par des virgules # /!\ explode peut exploser aussi DECIMAL(10,2) ! $k_precedent = null; foreach (explode(",", $desc) as $v){ preg_match("/^\s*([^\s]+)\s+(.*)/", $v, $r); // Les cles de champs peuvent etre entourees // de guillements doubles " , simples ', graves ` ou de crochets [ ], ou rien. // http://www.sqlite.org/lang_keywords.html $k = strtolower(query_reinjecte_textes($r[1], $echaps)); // champ, "champ", [champ]... if ($char = strpbrk($k[0], '\'"[`')) { $k = trim($k, $char); if ($char == '[') $k = rtrim($k, ']'); } $def = query_reinjecte_textes($r[2], $echaps); // valeur du champ # rustine pour DECIMAL(10,2) if (false !== strpos($k, ')')) { $fields[$k_precedent] .= ',' . $k . ' ' . $def; continue; } $fields[$k] = $def; $k_precedent = $k; // la primary key peut etre dans une des descriptions de champs // et non en fin de table, cas encore decouvert avec Sqlite Manager if (stripos($r[2], 'PRIMARY KEY') !== false) { $keys['PRIMARY KEY'] = $k; } } // key inclues dans la requete foreach(preg_split('/\)\s*(,|$)/',$namedkeys) as $v) { if (preg_match("/^\s*([^(]*)\(([^(]*(\(\d+\))?)$/",$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, $serveur); 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 * * @return void */ 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 * * @param string $version * @param string $link * @param string $serveur * @param bool $requeter * @return bool|int */ function _sqlite_is_version($version = '', $link = '', $serveur = '', $requeter = true){ if ($link==='') $link = _sqlite_link($serveur); if (!$link) return false; if ($link instanceof PDO){ $v = 3; } else { $v = 2; } if (!$version) return $v; return ($version==$v); } /** * retrouver un link * http://doc.spip.org/@_sqlite_link * * @param string $serveur * @return */ function _sqlite_link($serveur = ''){ $link = &$GLOBALS['connexions'][$serveur ? $serveur : 0]['link']; return $link; } /* ordre alphabetique pour les autres */ /** * renvoie les bons echappements (pas sur les fonctions now()) * http://doc.spip.org/@_sqlite_calculer_cite * * @param string|array|number $v * @param string $type * @return string|array|number */ function _sqlite_calculer_cite($v, $type){ if ($type){ if(is_null($v) AND stripos($type,"NOT NULL")===false) return 'NULL'; // null php se traduit en NULL SQL if (sql_test_date($type) AND preg_match('/^\w+\(/', $v)) return $v; if (sql_test_int($type)){ if (is_numeric($v)) return $v; elseif (ctype_xdigit(substr($v, 2)) AND strncmp($v, '0x', 2)==0) return hexdec(substr($v, 2)); else return intval($v); } } else { // si on ne connait pas le type on le deduit de $v autant que possible if (is_numeric($v)) return strval($v); } 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); } } // echapper les ' en '' spip_log("Pas de methode sqlite_escape_string ni ->quote pour echapper","sqlite."._LOG_INFO_IMPORTANTE); return ("'" . str_replace("'","''",$v) . "'"); } /** * renvoie grosso modo "$expression join($join, $v)" * http://doc.spip.org/@_sqlite_calculer_expression * * @param $expression * @param $v * @param string $join * @return string */ 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 * * @param $orderby * @return string */ 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); } /** * renvoie les bonnes parentheses pour des where imbriquees * http://doc.spip.org/@_sqlite_calculer_where * * @param $v * @return array|mixed|string */ 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 * * @param string $version * @return array|bool */ 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) * * http://doc.spip.org/@_sqlite_modifier_table * * @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 * */ 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); if (!$def_origine OR !isset($def_origine['field'])){ spip_log("Alter table impossible sur $table_origine : table non trouvee",'sqlite'._LOG_ERREUR); return false; } $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(); // copier dans destination (si differente de origine), sinon tmp $table_copie = ($meme_table) ? $table_tmp : $table_destination; $autoinc = (isset($keys['PRIMARY KEY']) AND stripos($keys['PRIMARY KEY'],',')===false AND stripos($fields[$keys['PRIMARY KEY']],'default')===false); if ($q = _sqlite_requete_create( $table_copie, $fields, $keys, $autoinc, $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, $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)"; } } if (count($queries)){ spip_sqlite::demarrer_transaction($serveur); // il faut les faire une par une car $query = join('; ', $queries).";"; ne fonctionne pas foreach ($queries as $q){ if (!spip_sqlite::executer_requete($q, $serveur)){ spip_log(_LOG_GRAVITE_ERREUR, "SQLite : ALTER TABLE table :" ." Erreur a l'execution de la requete : $q", 'sqlite'); spip_sqlite::annuler_transaction($serveur); return false; } } spip_sqlite::finir_transaction($serveur); } return true; } /** * Nom des fonctions * http://doc.spip.org/@_sqlite_ref_fonctions * * @return array */ 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', 'preferer_transaction' => 'spip_sqlite_preferer_transaction', 'demarrer_transaction' => 'spip_sqlite_demarrer_transaction', 'terminer_transaction' => 'spip_sqlite_terminer_transaction', ); // 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 * * @param $query * @param bool $autoinc * @return mixed */ function _sqlite_remplacements_definitions_table($query, $autoinc = false){ // quelques remplacements $num = "(\s*\([0-9]*\))?"; $enum = "(\s*\([^\)]*\))?"; $remplace = array( '/enum'.$enum.'/is' => 'VARCHAR(255)', '/COLLATE \w+_bin/is' => 'COLLATE BINARY', '/COLLATE \w+_ci/is' => 'COLLATE NOCASE', '/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+collate\s+\w+)?)\s*$/is' => "\\1 DEFAULT ''", '/((char|varchar)'.$num.'\s+not\s+null(\s+collate\s+\w+)?)\s*$/is' => "\\1 DEFAULT ''", '/(datetime\s+not\s+null)\s*$/is' => "\\1 DEFAULT '0000-00-00 00:00:00'", '/(date\s+not\s+null)\s*$/is' => "\\1 DEFAULT '0000-00-00'", ); // pour l'autoincrement, il faut des INTEGER NOT NULL PRIMARY KEY $remplace_autocinc = array( '/(big|small|medium|tiny)?int(eger)?'.$num.'/is' => 'INTEGER' ); // pour les int non autoincrement, il faut un DEFAULT $remplace_nonautocinc = array( '/((big|small|medium|tiny)?int(eger)?'.$num.'\s+not\s+null)\s*$/is' => "\\1 DEFAULT 0", ); if (is_string($query)){ $query = preg_replace(array_keys($remplace), $remplace, $query); if ($autoinc OR preg_match(',AUTO_INCREMENT,is',$query)) $query = preg_replace(array_keys($remplace_autocinc), $remplace_autocinc, $query); else{ $query = preg_replace(array_keys($remplace_nonautocinc), $remplace_nonautocinc, $query); $query = _sqlite_collate_ci($query); } } elseif(is_array($query)){ foreach($query as $k=>$q) { $ai = ($autoinc?$k==$autoinc:preg_match(',AUTO_INCREMENT,is',$q)); $query[$k] = preg_replace(array_keys($remplace), $remplace, $query[$k]); if ($ai) $query[$k] = preg_replace(array_keys($remplace_autocinc), $remplace_autocinc, $query[$k]); else{ $query[$k] = preg_replace(array_keys($remplace_nonautocinc), $remplace_nonautocinc, $query[$k]); $query[$k] = _sqlite_collate_ci($query[$k]); } } } return $query; } /** * Definir la collation d'un champ en fonction de si une collation est deja explicite * et du par defaut que l'on veut NOCASE * @param string $champ * @return string */ function _sqlite_collate_ci($champ){ if (stripos($champ,"COLLATE")!==false) return $champ; if (stripos($champ,"BINARY")!==false) return str_ireplace("BINARY","COLLATE BINARY",$champ); if (preg_match(",^(char|varchar|(long|small|medium|tiny)?text),i",$champ)) return $champ . " COLLATE NOCASE"; return $champ; } /** * 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 * * @param $nom * @param $champs * @param $cles * @param bool $autoinc * @param bool $temporary * @param bool $_ifnotexists * @param string $serveur * @param bool $requeter * @return bool|string */ 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 $c = ""; // le champ de cle primaire if (!isset($cles[$pk = "PRIMARY KEY"]) OR !$c = $cles[$pk]){ 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)"; // Pas de DEFAULT 0 sur les cles primaires en auto-increment if (isset($champs[$c]) AND stripos($champs[$c],"default 0")!==false){ $champs[$c] = trim(str_ireplace("default 0","",$champs[$c])); } $champs = _sqlite_remplacements_definitions_table($champs, $autoinc?$c:false); foreach ($champs as $k => $v){ $query .= "$s\n\t\t$k $v"; $s = ","; } $ifnotexists = ""; if ($_ifnotexists){ $version = spip_sqlite_fetch(spip_sqlite_query("select sqlite_version() AS sqlite_version",$serveur),'',$serveur); if (!function_exists('spip_version_compare')) include_spip('plugins/installer'); if ($version AND spip_version_compare($version['sqlite_version'],'3.3.0','>=')) { $ifnotexists = ' IF NOT EXISTS'; } else { /* simuler le IF EXISTS - version 2 et sqlite < 3.3a */ $a = spip_sqlite_showtable($table, $serveur); if (isset($a['key']['KEY '.$nom])) return true; } } $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 * * @param $table * @param $couples * @param string $desc * @param string $serveur * @return */ 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) $tables[$table][$k] = "datetime('now')"; } } // ajout des champs type 'timestamp' absents return array_merge($tables[$table],$couples); } /** * renvoyer la liste des versions sqlite disponibles * sur le serveur * http://doc.spip.org/@spip_versions_sqlite * * @return array|bool */ function spip_versions_sqlite(){ return _sqlite_charger_version(); } class spip_sqlite { static $requeteurs = array(); static $transaction_en_cours = array(); function spip_sqlite(){} /** * Retourne une unique instance du requêteur * * Retourne une instance unique du requêteur pour une connexion SQLite * donnée * * @param string $serveur * Nom du connecteur * @return sqlite_requeteur * Instance unique du requêteur **/ static function requeteur($serveur){ if (!isset(spip_sqlite::$requeteurs[$serveur])) spip_sqlite::$requeteurs[$serveur] = new sqlite_requeteur($serveur); return spip_sqlite::$requeteurs[$serveur]; } static function traduire_requete($query, $serveur){ $requeteur = spip_sqlite::requeteur($serveur); $traducteur = new sqlite_traducteur($query, $requeteur->prefixe,$requeteur->sqlite_version); return $traducteur->traduire_requete(); } static function demarrer_transaction($serveur){ spip_sqlite::executer_requete("BEGIN TRANSACTION",$serveur); spip_sqlite::$transaction_en_cours[$serveur] = true; } static function executer_requete($query, $serveur, $tracer=null){ $requeteur = spip_sqlite::requeteur($serveur); return $requeteur->executer_requete($query, $tracer); } static function last_insert_id($serveur){ $requeteur = spip_sqlite::requeteur($serveur); return $requeteur->last_insert_id($serveur); } static function annuler_transaction($serveur){ spip_sqlite::executer_requete("ROLLBACK",$serveur); spip_sqlite::$transaction_en_cours[$serveur] = false; } static function finir_transaction($serveur){ // si pas de transaction en cours, ne rien faire et le dire if (!isset (spip_sqlite::$transaction_en_cours[$serveur]) OR spip_sqlite::$transaction_en_cours[$serveur]==false) return false; // sinon fermer la transaction et retourner true spip_sqlite::executer_requete("COMMIT",$serveur); spip_sqlite::$transaction_en_cours[$serveur] = false; return true; } } /* * Classe pour partager les lancements de requete * instanciee une fois par $serveur * - peut corriger la syntaxe des requetes pour la conformite a sqlite * - peut tracer les requetes * */ class sqlite_requeteur { var $query = ''; // la requete 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) /** * constructeur * http://doc.spip.org/@sqlite_traiter_requete * * @param $query * @param string $serveur * @return bool */ function sqlite_requeteur($serveur = ''){ _sqlite_init(); $this->serveur = strtolower($serveur); if (!($this->link = _sqlite_link($this->serveur)) && (!defined('_ECRIRE_INSTALL') || !_ECRIRE_INSTALL)){ spip_log("Aucune connexion sqlite (link)", 'sqlite.'._LOG_ERREUR); 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 $query, * faire le tracage si demande * http://doc.spip.org/@executer_requete * * @return bool|SQLiteResult */ function executer_requete($query, $tracer=null){ if (is_null($tracer)) $tracer = $this->tracer; $err = ""; $t = 0; if ($tracer){ include_spip('public/tracer'); $t = trace_query_start(); } # spip_log("requete: $this->serveur >> $query",'sqlite.'._LOG_DEBUG); // boum ? pourquoi ? if ($this->link){ // memoriser la derniere erreur PHP vue $e = (function_exists('error_get_last')?error_get_last():""); // sauver la derniere requete $GLOBALS['connexions'][$this->serveur ? $this->serveur : 0]['last'] = $query; if ($this->sqlite_version==3){ $r = $this->link->query($query); // sauvegarde de la requete (elle y est deja dans $r->queryString) # $r->spipQueryString = $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($query), 0, 6))=='SELECT'){ if ($r){ // noter le link et la query pour faire le comptage *si* on en a besoin $r->spipSqliteRowCount = array($this->link,$query); } elseif ($r instanceof PDOStatement) { $r->spipSqliteRowCount = 0; } } } else { $r = sqlite_query($this->link, $query); } // loger les warnings/erreurs eventuels de sqlite remontant dans PHP if ($err = (function_exists('error_get_last')?error_get_last():"") AND $err!=$e){ $err = strip_tags($err['message'])." in ".$err['file']." line ".$err['line']; spip_log("$err - ".$query, 'sqlite.'._LOG_ERREUR); } else $err = ""; } else { $r = false; } if (spip_sqlite_errno($this->serveur)) $err .= spip_sqlite_error($query, $this->serveur); return $t ? trace_query_end($query, $t, $r, $err, $this->serveur) : $r; } function last_insert_id(){ if ($this->sqlite_version==3) return $this->link->lastInsertId(); else return sqlite_last_insert_rowid($this->link); } } /** * 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_traducteur { var $query = ''; var $prefixe = ''; // le prefixe des tables var $sqlite_version = ''; // Version de sqlite (2 ou 3) // Pour les corrections a effectuer sur les requetes : var $textes = array(); // array(code=>'texte') trouvé function sqlite_traducteur($query, $prefixe, $sqlite_version){ $this->query = $query; $this->prefixe = $prefixe; $this->sqlite_version = $sqlite_version; } /** * 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 * * @return void */ 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.'._LOG_AVERTISSEMENT); $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.'._LOG_DEBUG); $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); } if (strpos($this->query, 'LEFT(')!==false){ $this->query = str_replace('LEFT(','_LEFT(',$this->query); } if (strpos($this->query, 'TIMESTAMPDIFF(')!==false){ $this->query = preg_replace('/TIMESTAMPDIFF\(\s*([^,]*)\s*,/Uims',"TIMESTAMPDIFF('\\1',",$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.'._LOG_ERREUR); $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/iS', $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_/S', '\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 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); return $this->query; } /** * les callbacks * remplacer DATE_ / INTERVAL par DATE...strtotime * http://doc.spip.org/@_remplacerDateParTime * * @param $matches * @return string */ function _remplacerDateParTime($matches){ $op = strtoupper($matches[1]=='ADD') ? '+' : '-'; return "datetime$matches[2] '$op$matches[3] $matches[4]')"; } /** * 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 * * @param $matches * @return string */ 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 "; } } ?>