X-Git-Url: http://git.cyclocoop.org/?p=velocampus%2Fweb%2Fwww.git;a=blobdiff_plain;f=www%2Fecrire%2Freq%2Fsqlite_generique.php;fp=www%2Fecrire%2Freq%2Fsqlite_generique.php;h=96116f9adf7b1e1c5cd4f252b50787fba1a410c8;hp=0000000000000000000000000000000000000000;hb=80b4d3e85f78d402ed2e73f8f5d1bf4c19962eed;hpb=aaf970bf4cdaf76689ecc10609048e18d073820c diff --git a/www/ecrire/req/sqlite_generique.php b/www/ecrire/req/sqlite_generique.php new file mode 100644 index 0000000..96116f9 --- /dev/null +++ b/www/ecrire/req/sqlite_generique.php @@ -0,0 +1,1905 @@ +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)) { + $table_origine = array_shift(array_keys($table)); + $table_destination = array_shift($table); + } else { + $table_origine = $table_destination = $table; + } + // ne prend actuellement qu'un changement + // mais pourra etre adapte pour changer plus qu'une colonne a la fois + if (is_array($colonne)) { + $colonne_origine = array_shift(array_keys($colonne)); + $colonne_destination = array_shift($colonne); + } else { + $colonne_origine = $colonne_destination = $colonne; + } + if (!isset($opt['field'])) $opt['field'] = array(); + if (!isset($opt['key'])) $opt['key'] = array(); + + // si les noms de tables sont differents, pas besoin de table temporaire + // on prendra directement le nom de la future table + $meme_table = ($table_origine == $table_destination); + + $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é + var $codeEchappements = "%@##@%"; + + + // 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 echappements '' + $this->query = str_replace("''", $this->codeEchappements, $this->query); + // enlever les 'textes' + $this->textes = array(); // vider + $this->query = preg_replace_callback("/('[^']*')/", array(&$this, '_remplacerTexteParCode'), $this->query); + + // + // 2) Corrections de la requete + // + // Correction Create Database + // Create Database -> requete ignoree + if (strpos($this->query, 'CREATE DATABASE')===0){ + spip_log("Sqlite : requete non executee -> $this->query","sqlite"); + $this->query = "SELECT 1"; + } + + // Correction Insert Ignore + // INSERT IGNORE -> insert (tout court et pas 'insert or replace') + if (strpos($this->query, 'INSERT IGNORE')===0){ + #spip_log("Sqlite : requete transformee -> $this->query","sqlite"); + $this->query = 'INSERT ' . substr($this->query,'13'); + } + + // Correction des dates avec INTERVAL + // utiliser sql_date_proche() de preference + if (strpos($this->query, 'INTERVAL')!==false){ + $this->query = preg_replace_callback("/DATE_(ADD|SUB).*INTERVAL\s+(\d+)\s+([a-zA-Z]+)\)/U", + array(&$this, '_remplacerDateParTime'), + $this->query); + } + + // 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 + // + // remettre les 'textes' + foreach ($this->textes as $cle=>$val){ + $this->query = str_replace($cle, $val, $this->query); + } + // remettre les echappements '' + $this->query = str_replace($this->codeEchappements,"''",$this->query); + } + + + + // 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; + } + +} + +?>