25831895c657ec70783f0cf4af85ead0ac956a78
[lhc/web/clavette_www.git] / www / ecrire / req / sqlite_generique.php
1 <?php
2
3 /* *************************************************************************\
4 * SPIP, Systeme de publication pour l'internet *
5 * *
6 * Copyright (c) 2001-2014 *
7 * Arnaud Martin, Antoine Pitrou, Philippe Riviere, Emmanuel Saint-James *
8 * *
9 * Ce programme est un logiciel libre distribue sous licence GNU/GPL. *
10 * Pour plus de details voir le fichier COPYING.txt ou l'aide en ligne. *
11 \***************************************************************************/
12
13 /**
14 * Ce fichier contient les fonctions gerant
15 * les instructions SQL pour Sqlite
16 *
17 * @package SPIP\SQL\SQLite
18 */
19
20 if (!defined('_ECRIRE_INC_VERSION')) return;
21
22 // TODO: get/set_caracteres ?
23
24
25 /*
26 *
27 * regroupe le maximum de fonctions qui peuvent cohabiter
28 * D'abord les fonctions d'abstractions de SPIP
29 *
30 */
31 // http://doc.spip.org/@req_sqlite_dist
32 function req_sqlite_dist($addr, $port, $login, $pass, $db = '', $prefixe = '', $sqlite_version = ''){
33 static $last_connect = array();
34
35 // si provient de selectdb
36 // un code pour etre sur que l'on vient de select_db()
37 if (strpos($db, $code = '@selectdb@')!==false){
38 foreach (array('addr', 'port', 'login', 'pass', 'prefixe') as $a){
39 $$a = $last_connect[$a];
40 }
41 $db = str_replace($code, '', $db);
42 }
43
44 /*
45 * En sqlite, seule l'adresse du fichier est importante.
46 * Ce sera $db le nom,
47 * le path est $addr
48 * (_DIR_DB si $addr est vide)
49 */
50 _sqlite_init();
51
52 // determiner le dossier de la base : $addr ou _DIR_DB
53 $f = _DIR_DB;
54 if ($addr AND strpos($addr, '/')!==false)
55 $f = rtrim($addr, '/').'/';
56
57 // un nom de base demande et impossible d'obtenir la base, on s'en va :
58 // il faut que la base existe ou que le repertoire parent soit writable
59 if ($db AND !is_file($f .= $db.'.sqlite') AND !is_writable(dirname($f))){
60 spip_log("base $f non trouvee ou droits en ecriture manquants", 'sqlite.'._LOG_HS);
61 return false;
62 }
63
64 // charger les modules sqlite au besoin
65 if (!_sqlite_charger_version($sqlite_version)){
66 spip_log("Impossible de trouver/charger le module SQLite ($sqlite_version)!", 'sqlite.'._LOG_HS);
67 return false;
68 }
69
70 // chargement des constantes
71 // il ne faut pas definir les constantes avant d'avoir charge les modules sqlite
72 $define = "spip_sqlite".$sqlite_version."_constantes";
73 $define();
74
75 $ok = false;
76 if (!$db){
77 // si pas de db ->
78 // base temporaire tant qu'on ne connait pas son vrai nom
79 // pour tester la connexion
80 $db = "_sqlite".$sqlite_version."_install";
81 $tmp = _DIR_DB.$db.".sqlite";
82 if ($sqlite_version==3){
83 $ok = $link = new PDO("sqlite:$tmp");
84 } else {
85 $ok = $link = sqlite_open($tmp, _SQLITE_CHMOD, $err);
86 }
87 } else {
88 // Ouvrir (eventuellement creer la base)
89 // si pas de version fourni, on essaie la 3, sinon la 2
90 if ($sqlite_version==3){
91 $ok = $link = new PDO("sqlite:$f");
92 } else {
93 $ok = $link = sqlite_open($f, _SQLITE_CHMOD, $err);
94 }
95 }
96
97 if (!$ok){
98 $e = sqlite_last_error($db);
99 spip_log("Impossible d'ouvrir la base SQLite($sqlite_version) $f : $e", 'sqlite.'._LOG_HS);
100 return false;
101 }
102
103 if ($link){
104 $last_connect = array(
105 'addr' => $addr,
106 'port' => $port,
107 'login' => $login,
108 'pass' => $pass,
109 'db' => $db,
110 'prefixe' => $prefixe,
111 );
112 // etre sur qu'on definit bien les fonctions a chaque nouvelle connexion
113 include_spip('req/sqlite_fonctions');
114 _sqlite_init_functions($link);
115 }
116
117 return array(
118 'db' => $db,
119 'prefixe' => $prefixe ? $prefixe : $db,
120 'link' => $link,
121 );
122 }
123
124
125 /**
126 * Fonction de requete generale, munie d'une trace a la demande
127 *
128 * @param string $query
129 * Requete a executer
130 * @param string $serveur
131 * Nom du connecteur
132 * @param bool $requeter
133 * Effectuer la requete ?
134 * - true pour executer
135 * - false pour retourner le texte de la requete
136 * @return bool|SQLiteResult|string
137 * Resultat de la requete
138 */
139 function spip_sqlite_query($query, $serveur = '', $requeter = true){
140 #spip_log("spip_sqlite_query() > $query",'sqlite.'._LOG_DEBUG);
141 #_sqlite_init(); // fait la premiere fois dans spip_sqlite
142 $query = spip_sqlite::traduire_requete($query, $serveur);
143 if (!$requeter) return $query;
144 return spip_sqlite::executer_requete($query, $serveur);
145 }
146
147
148 /* ordre alphabetique pour les autres */
149
150 // http://doc.spip.org/@spip_sqlite_alter
151 function spip_sqlite_alter($query, $serveur = '', $requeter = true){
152
153 $query = spip_sqlite_query("ALTER $query", $serveur, false);
154 // traduire la requete pour recuperer les bons noms de table
155 $query = spip_sqlite::traduire_requete($query, $serveur);
156
157 /*
158 * la il faut faire les transformations
159 * si ALTER TABLE x (DROP|CHANGE) y
160 *
161 * 1) recuperer "ALTER TABLE table "
162 * 2) spliter les sous requetes (,)
163 * 3) faire chaque requete independemment
164 */
165
166 // 1
167 if (preg_match("/\s*(ALTER(\s*IGNORE)?\s*TABLE\s*([^\s]*))\s*(.*)?/is", $query, $regs)){
168 $debut = $regs[1];
169 $table = $regs[3];
170 $suite = $regs[4];
171 } else {
172 spip_log("SQLite : Probleme de ALTER TABLE mal forme dans $query", 'sqlite.'._LOG_ERREUR);
173 return false;
174 }
175
176 // 2
177 // il faudrait une regexp pour eviter de spliter ADD PRIMARY KEY (colA, colB)
178 // tout en cassant "ADD PRIMARY KEY (colA, colB), ADD INDEX (chose)"... en deux
179 // ou revoir l'api de sql_alter en creant un
180 // sql_alter_table($table,array($actions));
181 $todo = explode(',', $suite);
182
183 // on remet les morceaux dechires ensembles... que c'est laid !
184 $todo2 = array();
185 $i = 0;
186 $ouverte = false;
187 while ($do = array_shift($todo)){
188 $todo2[$i] = isset($todo2[$i]) ? $todo2[$i].",".$do : $do;
189 $o = (false!==strpos($do, "("));
190 $f = (false!==strpos($do, ")"));
191 if ($o AND !$f) $ouverte = true;
192 elseif ($f) $ouverte = false;
193 if (!$ouverte) $i++;
194 }
195
196 // 3
197 $resultats = array();
198 foreach ($todo2 as $do){
199 $do = trim($do);
200 if (!preg_match('/(DROP PRIMARY KEY|DROP KEY|DROP INDEX|DROP COLUMN|DROP'
201 .'|CHANGE COLUMN|CHANGE|MODIFY|RENAME TO|RENAME'
202 .'|ADD PRIMARY KEY|ADD KEY|ADD INDEX|ADD UNIQUE KEY|ADD UNIQUE'
203 .'|ADD COLUMN|ADD'
204 .')\s*([^\s]*)\s*(.*)?/i', $do, $matches)){
205 spip_log("SQLite : Probleme de ALTER TABLE, utilisation non reconnue dans : $do \n(requete d'origine : $query)", 'sqlite.'._LOG_ERREUR);
206 return false;
207 }
208
209 $cle = strtoupper($matches[1]);
210 $colonne_origine = $matches[2];
211 $colonne_destination = '';
212
213 $def = $matches[3];
214
215 // eluder une eventuelle clause before|after|first inutilisable
216 $defr = rtrim(preg_replace('/(BEFORE|AFTER|FIRST)(.*)$/is', '', $def));
217 $defo = $defr; // garder la def d'origine pour certains cas
218 // remplacer les definitions venant de mysql
219 $defr = _sqlite_remplacements_definitions_table($defr);
220
221 // reinjecter dans le do
222 $do = str_replace($def, $defr, $do);
223 $def = $defr;
224
225 switch ($cle) {
226 // suppression d'un index
227 case 'DROP KEY':
228 case 'DROP INDEX':
229 $nom_index = $colonne_origine;
230 spip_sqlite_drop_index($nom_index, $table, $serveur);
231 break;
232
233 // suppression d'une pk
234 case 'DROP PRIMARY KEY':
235 if (!_sqlite_modifier_table(
236 $table,
237 $colonne_origine,
238 array('key' => array('PRIMARY KEY' => '')),
239 $serveur)){
240 return false;
241 }
242 break;
243 // suppression d'une colonne
244 case 'DROP COLUMN':
245 case 'DROP':
246 if (!_sqlite_modifier_table(
247 $table,
248 array($colonne_origine => ""),
249 '',
250 $serveur)){
251 return false;
252 }
253 break;
254
255 case 'CHANGE COLUMN':
256 case 'CHANGE':
257 // recuperer le nom de la future colonne
258 // on reprend la def d'origine car _sqlite_modifier_table va refaire la translation
259 // en tenant compte de la cle primaire (ce qui est mieux)
260 $def = trim($defo);
261 $colonne_destination = substr($def, 0, strpos($def, ' '));
262 $def = substr($def, strlen($colonne_destination)+1);
263
264 if (!_sqlite_modifier_table(
265 $table,
266 array($colonne_origine => $colonne_destination),
267 array('field' => array($colonne_destination => $def)),
268 $serveur)){
269 return false;
270 }
271 break;
272
273 case 'MODIFY':
274 // on reprend la def d'origine car _sqlite_modifier_table va refaire la translation
275 // en tenant compte de la cle primaire (ce qui est mieux)
276 if (!_sqlite_modifier_table(
277 $table,
278 $colonne_origine,
279 array('field' => array($colonne_origine => $defo)),
280 $serveur)){
281 return false;
282 }
283 break;
284
285 // pas geres en sqlite2
286 case 'RENAME':
287 $do = "RENAME TO".substr($do, 6);
288 case 'RENAME TO':
289 if (_sqlite_is_version(3, '', $serveur)){
290 if (!spip_sqlite::executer_requete("$debut $do", $serveur)){
291 spip_log("SQLite : Erreur ALTER TABLE / RENAME : $query", 'sqlite.'._LOG_ERREUR);
292 return false;
293 }
294 // artillerie lourde pour sqlite2 !
295 } else {
296 $table_dest = trim(substr($do, 9));
297 if (!_sqlite_modifier_table(array($table => $table_dest), '', '', $serveur)){
298 spip_log("SQLite : Erreur ALTER TABLE / RENAME : $query", 'sqlite.'._LOG_ERREUR);
299 return false;
300 }
301 }
302 break;
303
304 // ajout d'une pk
305 case 'ADD PRIMARY KEY':
306 $pk = trim(substr($do, 16));
307 $pk = ($pk[0]=='(') ? substr($pk, 1, -1) : $pk;
308 if (!_sqlite_modifier_table(
309 $table,
310 $colonne_origine,
311 array('key' => array('PRIMARY KEY' => $pk)),
312 $serveur)){
313 return false;
314 }
315 break;
316 // ajout d'un index
317 case 'ADD UNIQUE KEY':
318 case 'ADD UNIQUE':
319 $unique=true;
320 case 'ADD INDEX':
321 case 'ADD KEY':
322 // peut etre "(colonne)" ou "nom_index (colonnes)"
323 // bug potentiel si qqn met "(colonne, colonne)"
324 //
325 // nom_index (colonnes)
326 if ($def){
327 $colonnes = substr($def, 1, -1);
328 $nom_index = $colonne_origine;
329 }
330 else {
331 // (colonne)
332 if ($colonne_origine[0]=="("){
333 $colonnes = substr($colonne_origine, 1, -1);
334 if (false!==strpos(",", $colonnes)){
335 spip_log(_LOG_GRAVITE_ERREUR, "SQLite : Erreur, impossible de creer un index sur plusieurs colonnes"
336 ." sans qu'il ait de nom ($table, ($colonnes))", 'sqlite');
337 break;
338 } else {
339 $nom_index = $colonnes;
340 }
341 }
342 // nom_index
343 else {
344 $nom_index = $colonnes = $colonne_origine;
345 }
346 }
347 spip_sqlite_create_index($nom_index, $table, $colonnes, $unique, $serveur);
348 break;
349
350 // pas geres en sqlite2
351 case 'ADD COLUMN':
352 $do = "ADD".substr($do, 10);
353 case 'ADD':
354 default:
355 if (_sqlite_is_version(3, '', $serveur) AND !preg_match(',primary\s+key,i',$do)){
356 if (!spip_sqlite::executer_requete("$debut $do", $serveur)){
357 spip_log("SQLite : Erreur ALTER TABLE / ADD : $query", 'sqlite.'._LOG_ERREUR);
358 return false;
359 }
360 break;
361
362 }
363 // artillerie lourde pour sqlite2 !
364 // ou si la colonne est aussi primary key
365 // cas du add id_truc int primary key
366 // ajout d'une colonne qui passe en primary key directe
367 else {
368 $def = trim(substr($do, 3));
369 $colonne_ajoutee = substr($def, 0, strpos($def, ' '));
370 $def = substr($def, strlen($colonne_ajoutee)+1);
371 $opts = array();
372 if (preg_match(',primary\s+key,i',$def)){
373 $opts['key'] = array('PRIMARY KEY' => $colonne_ajoutee);
374 $def = preg_replace(',primary\s+key,i','',$def);
375 }
376 $opts['field'] = array($colonne_ajoutee => $def);
377 if (!_sqlite_modifier_table($table, array($colonne_ajoutee), $opts, $serveur)){
378 spip_log("SQLite : Erreur ALTER TABLE / ADD : $query", 'sqlite.'._LOG_ERREUR);
379 return false;
380 }
381 }
382 break;
383 }
384 // tout est bon, ouf !
385 spip_log("SQLite ($serveur) : Changements OK : $debut $do", 'sqlite.'._LOG_INFO);
386 }
387
388 spip_log("SQLite ($serveur) : fin ALTER TABLE OK !", 'sqlite.'._LOG_INFO);
389 return true;
390 }
391
392
393 /**
394 * Fonction de creation d'une table SQL nommee $nom
395 * http://doc.spip.org/@spip_sqlite_create
396 *
397 * @param string $nom
398 * @param array $champs
399 * @param array $cles
400 * @param bool $autoinc
401 * @param bool $temporary
402 * @param string $serveur
403 * @param bool $requeter
404 * @return bool|SQLiteResult|string
405 */
406 function spip_sqlite_create($nom, $champs, $cles, $autoinc = false, $temporary = false, $serveur = '', $requeter = true){
407 $query = _sqlite_requete_create($nom, $champs, $cles, $autoinc, $temporary, $ifnotexists = true, $serveur, $requeter);
408 if (!$query) return false;
409 $res = spip_sqlite_query($query, $serveur, $requeter);
410
411 // SQLite ne cree pas les KEY sur les requetes CREATE TABLE
412 // il faut donc les faire creer ensuite
413 if (!$requeter) return $res;
414
415 $ok = $res ? true : false;
416 if ($ok){
417 foreach ($cles as $k => $v){
418 if (preg_match(',^(KEY|UNIQUE)\s,i',$k,$m)){
419 $index = trim(substr($k,strlen($m[1])));
420 $unique = (strlen($m[1])>3);
421 $ok &= spip_sqlite_create_index($index, $nom, $v, $unique, $serveur);
422 }
423 }
424 }
425 return $ok ? true : false;
426 }
427
428 /**
429 * Fonction pour creer une base de donnees SQLite
430 *
431 * @param string $nom le nom de la base (sans l'extension de fichier)
432 * @param string $serveur le nom de la connexion
433 * @param string $option options
434 *
435 * @return bool true si la base est creee.
436 **/
437 function spip_sqlite_create_base($nom, $serveur = '', $option = true){
438 $f = $nom.'.sqlite';
439 if (strpos($nom, "/")===false)
440 $f = _DIR_DB.$f;
441 if (_sqlite_is_version(2, '', $serveur)){
442 $ok = sqlite_open($f, _SQLITE_CHMOD, $err);
443 } else {
444 $ok = new PDO("sqlite:$f");
445 }
446 if ($ok){
447 unset($ok);
448 return true;
449 }
450 unset($ok);
451 return false;
452 }
453
454
455 /**
456 * Fonction de creation d'une vue SQL nommee $nom
457 * http://doc.spip.org/@spip_sqlite_create_view
458 *
459 * @param string $nom
460 * Nom de la vue a creer
461 * @param string $query_select
462 * Texte de la requete de selection servant de base a la vue
463 * @param string $serveur
464 * Nom du connecteur
465 * @param bool $requeter
466 * Effectuer la requete ?
467 * - true pour executer
468 * - false pour retourner le texte de la requete
469 * @return bool|SQLiteResult|string
470 * Resultat de la requete ou
471 * - false si erreur ou si la vue existe deja
472 * - string texte de la requete si $requeter vaut false
473 */
474 function spip_sqlite_create_view($nom, $query_select, $serveur = '', $requeter = true){
475 if (!$query_select) return false;
476 // vue deja presente
477 if (sql_showtable($nom, false, $serveur)){
478 spip_log("Echec creation d'une vue sql ($nom) car celle-ci existe deja (serveur:$serveur)", 'sqlite.'._LOG_ERREUR);
479 return false;
480 }
481
482 $query = "CREATE VIEW $nom AS ".$query_select;
483 return spip_sqlite_query($query, $serveur, $requeter);
484 }
485
486 /**
487 * Fonction de creation d'un INDEX
488 *
489 * @param string $nom : nom de l'index
490 * @param string $table : table sql de l'index
491 * @param string/array $champs : liste de champs sur lesquels s'applique l'index
492 * @param string $serveur : nom de la connexion sql utilisee
493 * @param bool $requeter : true pour executer la requete ou false pour retourner le texte de la requete
494 *
495 * @return bool ou requete
496 */
497 function spip_sqlite_create_index($nom, $table, $champs, $unique='', $serveur = '', $requeter = true){
498 if (!($nom OR $table OR $champs)){
499 spip_log("Champ manquant pour creer un index sqlite ($nom, $table, (".join(',', $champs)."))", 'sqlite.'._LOG_ERREUR);
500 return false;
501 }
502
503 // SQLite ne differentie pas noms des index en fonction des tables
504 // il faut donc creer des noms uniques d'index pour une base sqlite
505 $nom = $table.'_'.$nom;
506 // enlever d'eventuelles parentheses deja presentes sur champs
507 if (!is_array($champs)){
508 if ($champs[0]=="(") $champs = substr($champs, 1, -1);
509 $champs = array($champs);
510 // supprimer l'info de longueur d'index mysql en fin de champ
511 $champs = preg_replace(",\(\d+\)$,","",$champs);
512 }
513
514 $ifnotexists = "";
515 $version = spip_sqlite_fetch(spip_sqlite_query("select sqlite_version() AS sqlite_version",$serveur),'',$serveur);
516 if (!function_exists('spip_version_compare')) include_spip('plugins/installer');
517
518 if ($version AND spip_version_compare($version['sqlite_version'],'3.3.0','>=')) {
519 $ifnotexists = ' IF NOT EXISTS';
520 } else {
521 /* simuler le IF EXISTS - version 2 et sqlite < 3.3a */
522 $a = spip_sqlite_showtable($table, $serveur);
523 if (isset($a['key']['KEY '.$nom])) return true;
524 }
525
526 $query = "CREATE ".($unique?"UNIQUE ":"")."INDEX$ifnotexists $nom ON $table (".join(',', $champs).")";
527 $res = spip_sqlite_query($query, $serveur, $requeter);
528 if (!$requeter) return $res;
529 if ($res)
530 return true;
531 else
532 return false;
533 }
534
535 /**
536 * en PDO/sqlite3, il faut calculer le count par une requete count(*)
537 * pour les resultats de SELECT
538 * cela est fait sans spip_sqlite_query()
539 * http://doc.spip.org/@spip_sqlite_count
540 *
541 * @param $r
542 * @param string $serveur
543 * @param bool $requeter
544 * @return int
545 */
546 function spip_sqlite_count($r, $serveur = '', $requeter = true){
547 if (!$r) return 0;
548
549 if (_sqlite_is_version(3, '', $serveur)){
550 // select ou autre (insert, update,...) ?
551
552 // (link,requete) a compter
553 if (is_array($r->spipSqliteRowCount)){
554 list($link,$query) = $r->spipSqliteRowCount;
555 // amelioration possible a tester intensivement : pas de order by pour compter !
556 // $query = preg_replace(",ORDER BY .+(LIMIT\s|HAVING\s|GROUP BY\s|$),Uims","\\1",$query);
557 $query = "SELECT count(*) as zzzzsqlitecount FROM ($query)";
558 $l = $link->query($query);
559 $i = 0;
560 if ($l AND $z = $l->fetch())
561 $i = $z['zzzzsqlitecount'];
562 $r->spipSqliteRowCount = $i;
563 }
564 if (isset($r->spipSqliteRowCount)){
565 // Ce compte est faux s'il y a des limit dans la requete :(
566 // il retourne le nombre d'enregistrements sans le limit
567 return $r->spipSqliteRowCount;
568 } else {
569 return $r->rowCount();
570 }
571 } else {
572 return sqlite_num_rows($r);
573 }
574 }
575
576
577 // http://doc.spip.org/@spip_sqlite_countsel
578 function spip_sqlite_countsel($from = array(), $where = array(), $groupby = '', $having = array(), $serveur = '', $requeter = true){
579 $c = !$groupby ? '*' : ('DISTINCT '.(is_string($groupby) ? $groupby : join(',', $groupby)));
580 $r = spip_sqlite_select("COUNT($c)", $from, $where, '', '', '',
581 $having, $serveur, $requeter);
582 if ((is_resource($r) or is_object($r)) && $requeter){ // ressource : sqlite2, object : sqlite3
583 if (_sqlite_is_version(3, '', $serveur)){
584 list($r) = spip_sqlite_fetch($r, SPIP_SQLITE3_NUM, $serveur);
585 } else {
586 list($r) = spip_sqlite_fetch($r, SPIP_SQLITE2_NUM, $serveur);
587 }
588
589 }
590 return $r;
591 }
592
593
594 // http://doc.spip.org/@spip_sqlite_delete
595 function spip_sqlite_delete($table, $where = '', $serveur = '', $requeter = true){
596 $res = spip_sqlite_query(
597 _sqlite_calculer_expression('DELETE FROM', $table, ',')
598 ._sqlite_calculer_expression('WHERE', $where),
599 $serveur, $requeter);
600
601 // renvoyer la requete inerte si demandee
602 if (!$requeter) return $res;
603
604 if ($res){
605 $link = _sqlite_link($serveur);
606 if (_sqlite_is_version(3, $link)){
607 return $res->rowCount();
608 } else {
609 return sqlite_changes($link);
610 }
611 }
612 else
613 return false;
614 }
615
616
617 // http://doc.spip.org/@spip_sqlite_drop_table
618 function spip_sqlite_drop_table($table, $exist = '', $serveur = '', $requeter = true){
619 if ($exist) $exist = " IF EXISTS";
620
621 /* simuler le IF EXISTS - version 2 */
622 if ($exist && _sqlite_is_version(2, '', $serveur)){
623 $a = spip_sqlite_showtable($table, $serveur);
624 if (!$a) return true;
625 $exist = '';
626 }
627 if (spip_sqlite_query("DROP TABLE$exist $table", $serveur, $requeter))
628 return true;
629 else
630 return false;
631 }
632
633 /**
634 * supprime une vue
635 * http://doc.spip.org/@spip_sqlite_drop_view
636 *
637 * @param $view
638 * @param string $exist
639 * @param string $serveur
640 * @param bool $requeter
641 * @return bool|SQLiteResult|string
642 */
643 function spip_sqlite_drop_view($view, $exist = '', $serveur = '', $requeter = true){
644 if ($exist) $exist = " IF EXISTS";
645
646 /* simuler le IF EXISTS - version 2 */
647 if ($exist && _sqlite_is_version(2, '', $serveur)){
648 $a = spip_sqlite_showtable($view, $serveur);
649 if (!$a) return true;
650 $exist = '';
651 }
652
653 return spip_sqlite_query("DROP VIEW$exist $view", $serveur, $requeter);
654 }
655
656 /**
657 * Fonction de suppression d'un INDEX
658 *
659 * @param string $nom : nom de l'index
660 * @param string $table : table sql de l'index
661 * @param string $serveur : nom de la connexion sql utilisee
662 * @param bool $requeter : true pour executer la requete ou false pour retourner le texte de la requete
663 *
664 * @return bool ou requete
665 */
666 function spip_sqlite_drop_index($nom, $table, $serveur = '', $requeter = true){
667 if (!($nom OR $table)){
668 spip_log("Champ manquant pour supprimer un index sqlite ($nom, $table)", 'sqlite.'._LOG_ERREUR);
669 return false;
670 }
671
672 // SQLite ne differentie pas noms des index en fonction des tables
673 // il faut donc creer des noms uniques d'index pour une base sqlite
674 $index = $table.'_'.$nom;
675 $exist = " IF EXISTS";
676
677 /* simuler le IF EXISTS - version 2 */
678 if (_sqlite_is_version(2, '', $serveur)){
679 $a = spip_sqlite_showtable($table, $serveur);
680 if (!isset($a['key']['KEY '.$nom])) return true;
681 $exist = '';
682 }
683
684 $query = "DROP INDEX$exist $index";
685 return spip_sqlite_query($query, $serveur, $requeter);
686 }
687
688 /**
689 * Retourne la derniÚre erreur generée
690 *
691 * @param $serveur
692 * nom de la connexion
693 * @return string
694 * erreur eventuelle
695 **/
696 function spip_sqlite_error($query = '', $serveur = ''){
697 $link = _sqlite_link($serveur);
698
699 if (_sqlite_is_version(3, $link)){
700 $errs = $link->errorInfo();
701 /*
702 $errs[0]
703 numero SQLState ('HY000' souvent lors d'une erreur)
704 http://www.easysoft.com/developer/interfaces/odbc/sqlstate_status_return_codes.html
705 $errs[1]
706 numéro d'erreur SQLite (souvent 1 lors d'une erreur)
707 http://www.sqlite.org/c3ref/c_abort.html
708 $errs[2]
709 Le texte du message d'erreur
710 */
711 $s = '';
712 if (ltrim($errs[0],'0')) { // 00000 si pas d'erreur
713 $s = "$errs[2]";
714 }
715 } elseif ($link) {
716 $s = sqlite_error_string(sqlite_last_error($link));
717 } else {
718 $s = ": aucune ressource sqlite (link)";
719 }
720 if ($s) spip_log("$s - $query", 'sqlite.'._LOG_ERREUR);
721 return $s;
722 }
723
724 /**
725 * Retourne le numero de la derniĂšre erreur SQL
726 *
727 * Le numéro (en sqlite3/pdo) est un retour ODBC tel que (trÚs souvent) HY000
728 * http://www.easysoft.com/developer/interfaces/odbc/sqlstate_status_return_codes.html
729 *
730 * @param string $serveur
731 * nom de la connexion
732 * @return int|string
733 * 0 pas d'erreur
734 * 1 ou autre erreur (en sqlite 2)
735 * 'HY000/1' : numéro de l'erreur SQLState / numéro d'erreur interne SQLite (en sqlite 3)
736 **/
737 function spip_sqlite_errno($serveur = ''){
738 $link = _sqlite_link($serveur);
739
740 if (_sqlite_is_version(3, $link)){
741 $t = $link->errorInfo();
742 $s = ltrim($t[0],'0'); // 00000 si pas d'erreur
743 if ($s) $s .= ' / ' . $t[1]; // ajoute l'erreur du moteur SQLite
744 } elseif ($link) {
745 $s = sqlite_last_error($link);
746 } else {
747 $s = ": aucune ressource sqlite (link)";
748 }
749
750 if ($s) spip_log("Erreur sqlite $s", 'sqlite.'._LOG_ERREUR);
751
752 return $s ? $s : 0;
753 }
754
755
756 // http://doc.spip.org/@spip_sqlite_explain
757 function spip_sqlite_explain($query, $serveur = '', $requeter = true){
758 if (strpos(ltrim($query), 'SELECT')!==0) return array();
759
760 $query = spip_sqlite::traduire_requete($query, $serveur);
761 $query = 'EXPLAIN '.$query;
762 if (!$requeter) return $query;
763 // on ne trace pas ces requetes, sinon on obtient un tracage sans fin...
764 $r = spip_sqlite::executer_requete($query, $serveur, false);
765
766 return $r ? spip_sqlite_fetch($r, null, $serveur) : false; // hum ? etrange ca... a verifier
767 }
768
769
770 // http://doc.spip.org/@spip_sqlite_fetch
771 function spip_sqlite_fetch($r, $t = '', $serveur = '', $requeter = true){
772
773 $link = _sqlite_link($serveur);
774 $is_v3 = _sqlite_is_version(3, $link);
775 if (!$t)
776 $t = ($is_v3 ? SPIP_SQLITE3_ASSOC : SPIP_SQLITE2_ASSOC);
777
778 $retour = false;
779 if ($r)
780 $retour = ($is_v3 ? $r->fetch($t) : sqlite_fetch_array($r, $t));
781
782 // les version 2 et 3 parfois renvoie des 'table.titre' au lieu de 'titre' tout court ! pff !
783 // suppression de 'table.' pour toutes les cles (c'est un peu violent !)
784 // c'est couteux : on ne verifie que la premiere ligne pour voir si on le fait ou non
785 if ($retour
786 AND strpos(implode('',array_keys($retour)),'.')!==false){
787 foreach ($retour as $cle => $val){
788 if (($pos = strpos($cle, '.'))!==false){
789 $retour[substr($cle, $pos+1)] = &$retour[$cle];
790 unset($retour[$cle]);
791 }
792 }
793 }
794
795 return $retour;
796 }
797
798
799 function spip_sqlite_seek($r, $row_number, $serveur = '', $requeter = true){
800 if ($r){
801 $link = _sqlite_link($serveur);
802 if (_sqlite_is_version(3, $link)){
803 // encore un truc de bien fichu : PDO ne PEUT PAS faire de seek ou de rewind...
804 // je me demande si pour sqlite 3 il ne faudrait pas mieux utiliser
805 // les nouvelles fonctions sqlite3_xx (mais encore moins presentes...)
806 return false;
807 }
808 else {
809 return sqlite_seek($r, $row_number);
810 }
811 }
812 }
813
814
815 // http://doc.spip.org/@spip_sqlite_free
816 function spip_sqlite_free(&$r, $serveur = '', $requeter = true){
817 unset($r);
818 return true;
819 //return sqlite_free_result($r);
820 }
821
822
823 // http://doc.spip.org/@spip_sqlite_get_charset
824 function spip_sqlite_get_charset($charset = array(), $serveur = '', $requeter = true){
825 //$c = !$charset ? '' : (" LIKE "._q($charset['charset']));
826 //return spip_sqlite_fetch(sqlite_query(_sqlite_link($serveur), "SHOW CHARACTER SET$c"), NULL, $serveur);
827 }
828
829
830 // http://doc.spip.org/@spip_sqlite_hex
831 function spip_sqlite_hex($v){
832 return hexdec($v);
833 }
834
835
836 // http://doc.spip.org/@spip_sqlite_in
837 function spip_sqlite_in($val, $valeurs, $not = '', $serveur = '', $requeter = true){
838 $n = $i = 0;
839 $in_sql = "";
840 while ($n = strpos($valeurs, ',', $n+1)){
841 if ((++$i)>=255){
842 $in_sql .= "($val $not IN (".
843 substr($valeurs, 0, $n).
844 "))\n".
845 ($not ? "AND\t" : "OR\t");
846 $valeurs = substr($valeurs, $n+1);
847 $i = $n = 0;
848 }
849 }
850 $in_sql .= "($val $not IN ($valeurs))";
851
852 return "($in_sql)";
853 }
854
855
856 // http://doc.spip.org/@spip_sqlite_insert
857 function spip_sqlite_insert($table, $champs, $valeurs, $desc = '', $serveur = '', $requeter = true){
858
859 $query = "INSERT INTO $table ".($champs ? "$champs VALUES $valeurs" : "DEFAULT VALUES");
860 if ($r = spip_sqlite_query($query, $serveur, $requeter)){
861 if (!$requeter) return $r;
862 $nb = spip_sqlite::last_insert_id($serveur);
863 }
864 else
865 $nb = 0;
866
867 $err = spip_sqlite_error($query, $serveur);
868 // cas particulier : ne pas substituer la reponse spip_sqlite_query si on est en profilage
869 return isset($_GET['var_profile']) ? $r : $nb;
870
871 }
872
873
874 // http://doc.spip.org/@spip_sqlite_insertq
875 function spip_sqlite_insertq($table, $couples = array(), $desc = array(), $serveur = '', $requeter = true){
876 if (!$desc) $desc = description_table($table, $serveur);
877 if (!$desc) die("$table insertion sans description");
878 $fields = isset($desc['field']) ? $desc['field'] : array();
879
880 foreach ($couples as $champ => $val){
881 $couples[$champ] = _sqlite_calculer_cite($val, $fields[$champ]);
882 }
883
884 // recherche de champs 'timestamp' pour mise a jour auto de ceux-ci
885 $couples = _sqlite_ajouter_champs_timestamp($table, $couples, $desc, $serveur);
886
887 $cles = $valeurs = "";
888 if (count($couples)){
889 $cles = "(".join(',', array_keys($couples)).")";
890 $valeurs = "(".join(',', $couples).")";
891 }
892
893 return spip_sqlite_insert($table, $cles, $valeurs, $desc, $serveur, $requeter);
894 }
895
896
897 // http://doc.spip.org/@spip_sqlite_insertq_multi
898 function spip_sqlite_insertq_multi($table, $tab_couples = array(), $desc = array(), $serveur = '', $requeter = true){
899 if (!$desc) $desc = description_table($table, $serveur);
900 if (!$desc) die("$table insertion sans description");
901 if (!isset($desc['field']))
902 $desc['field'] = array();
903
904 // recuperer les champs 'timestamp' pour mise a jour auto de ceux-ci
905 $maj = _sqlite_ajouter_champs_timestamp($table, array(), $desc, $serveur);
906
907 // seul le nom de la table est a traduire ici :
908 // le faire une seule fois au debut
909 $query_start = "INSERT INTO $table ";
910 $query_start = spip_sqlite::traduire_requete($query_start,$serveur);
911
912 // ouvrir une transaction
913 if ($requeter)
914 spip_sqlite::demarrer_transaction($serveur);
915
916 while ($couples = array_shift($tab_couples)){
917 foreach ($couples as $champ => $val){
918 $couples[$champ] = _sqlite_calculer_cite($val, $desc['field'][$champ]);
919 }
920
921 // inserer les champs timestamp par defaut
922 $couples = array_merge($maj,$couples);
923
924 $champs = $valeurs = "";
925 if (count($couples)){
926 $champs = "(".join(',', array_keys($couples)).")";
927 $valeurs = "(".join(',', $couples).")";
928 $query = $query_start."$champs VALUES $valeurs";
929 }
930 else
931 $query = $query_start."DEFAULT VALUES";
932
933 if ($requeter)
934 $retour = spip_sqlite::executer_requete($query,$serveur);
935
936 // sur le dernier couple uniquement
937 if (!count($tab_couples)){
938 $nb = 0;
939 if ($requeter)
940 $nb = spip_sqlite::last_insert_id($serveur);
941 else
942 return $query;
943 }
944
945 $err = spip_sqlite_error($query, $serveur);
946 }
947
948 if ($requeter)
949 spip_sqlite::finir_transaction($serveur);
950
951 // renvoie le dernier id d'autoincrement ajoute
952 // cas particulier : ne pas substituer la reponse spip_sqlite_query si on est en profilage
953 return isset($_GET['var_profile']) ? $retour : $nb;
954 }
955
956
957 /**
958 * Retourne si le moteur SQL prefere utiliser des transactions.
959 *
960 * @param
961 * @return bool true / false
962 **/
963 function spip_sqlite_preferer_transaction($serveur = '', $requeter = true) {
964 return true;
965 }
966
967 /**
968 * Demarre une transaction.
969 * Pratique pour des sql_updateq() dans un foreach,
970 * parfois 100* plus rapide s'ils sont nombreux en sqlite !
971 *
972 **/
973 function spip_sqlite_demarrer_transaction($serveur = '', $requeter = true) {
974 if (!$requeter) return "BEGIN TRANSACTION";
975 spip_sqlite::demarrer_transaction($serveur);
976 return true;
977 }
978
979 /**
980 * Cloture une transaction.
981 *
982 **/
983 function spip_sqlite_terminer_transaction($serveur = '', $requeter = true) {
984 if (!$requeter) return "COMMIT";
985 spip_sqlite::finir_transaction($serveur);
986 return true;
987 }
988
989
990 // http://doc.spip.org/@spip_sqlite_listdbs
991 function spip_sqlite_listdbs($serveur = '', $requeter = true){
992 _sqlite_init();
993
994 if (!is_dir($d = substr(_DIR_DB, 0, -1))){
995 return array();
996 }
997
998 include_spip('inc/flock');
999 $bases = preg_files($d, $pattern = '(.*)\.sqlite$');
1000 $bds = array();
1001
1002 foreach ($bases as $b){
1003 // pas de bases commencant pas sqlite
1004 // (on s'en sert pour l'installation pour simuler la presence d'un serveur)
1005 // les bases sont de la forme _sqliteX_tmp_spip_install.sqlite
1006 if (strpos($b, '_sqlite')) continue;
1007 $bds[] = preg_replace(";.*/$pattern;iS", '$1', $b);
1008 }
1009
1010 return $bds;
1011 }
1012
1013
1014 // http://doc.spip.org/@spip_sqlite_multi
1015 function spip_sqlite_multi($objet, $lang){
1016 $r = "EXTRAIRE_MULTI(" . $objet . ", '" . $lang . "') AS multi";
1017 return $r;
1018 }
1019
1020
1021 /**
1022 * Optimise une table SQL
1023 * Note: Sqlite optimise TOUTE un fichier sinon rien.
1024 * On evite donc 2 traitements sur la meme base dans un hit.
1025 *
1026 * @param $table nom de la table a optimiser
1027 * @param $serveur nom de la connexion
1028 * @param $requeter effectuer la requete ? sinon retourner son code
1029 * @return bool|string true / false / requete
1030 **/
1031 // http://doc.spip.org/@spip_sqlite_optimize
1032 function spip_sqlite_optimize($table, $serveur = '', $requeter = true){
1033 static $do = false;
1034 if ($requeter and $do){
1035 return true;
1036 }
1037 if ($requeter){
1038 $do = true;
1039 }
1040 return spip_sqlite_query("VACUUM", $serveur, $requeter);
1041 }
1042
1043
1044 //
1045 /**
1046 * echapper une valeur selon son type ou au mieux
1047 * comme le fait _q() mais pour sqlite avec ses specificites
1048 *
1049 * @param string|array|number $v
1050 * @param string $type
1051 * @return string|number
1052 */
1053 function spip_sqlite_quote($v, $type = ''){
1054 if (!is_array($v))
1055 return _sqlite_calculer_cite($v,$type);
1056 // si c'est un tableau, le parcourir en propageant le type
1057 foreach($v as $k=>$r)
1058 $v[$k] = spip_sqlite_quote($r, $type);
1059 return join(",", $v);
1060 }
1061
1062
1063 /**
1064 * Tester si une date est proche de la valeur d'un champ
1065 *
1066 * @param string $champ le nom du champ a tester
1067 * @param int $interval valeur de l'interval : -1, 4, ...
1068 * @param string $unite utite utilisee (DAY, MONTH, YEAR, ...)
1069 * @return string expression SQL
1070 **/
1071 function spip_sqlite_date_proche($champ, $interval, $unite){
1072 $op = (($interval <= 0) ? '>' : '<');
1073 return "($champ $op datetime('".date("Y-m-d H:i:s")."', '$interval $unite'))";
1074 }
1075
1076
1077 // http://doc.spip.org/@spip_sqlite_replace
1078 function spip_sqlite_replace($table, $couples, $desc = array(), $serveur = '', $requeter = true){
1079 if (!$desc) $desc = description_table($table, $serveur);
1080 if (!$desc) die("$table insertion sans description");
1081 $fields = isset($desc['field']) ? $desc['field'] : array();
1082
1083 foreach ($couples as $champ => $val){
1084 $couples[$champ] = _sqlite_calculer_cite($val, $fields[$champ]);
1085 }
1086
1087 // recherche de champs 'timestamp' pour mise a jour auto de ceux-ci
1088 $couples = _sqlite_ajouter_champs_timestamp($table, $couples, $desc, $serveur);
1089
1090 return spip_sqlite_query("REPLACE INTO $table (".join(',', array_keys($couples)).') VALUES ('.join(',', $couples).')', $serveur);
1091 }
1092
1093
1094 // http://doc.spip.org/@spip_sqlite_replace_multi
1095 function spip_sqlite_replace_multi($table, $tab_couples, $desc = array(), $serveur = '', $requeter = true){
1096
1097 // boucler pour trainter chaque requete independemment
1098 foreach ($tab_couples as $couples){
1099 $retour = spip_sqlite_replace($table, $couples, $desc, $serveur, $requeter);
1100 }
1101 // renvoie le dernier id
1102 return $retour;
1103 }
1104
1105
1106 // http://doc.spip.org/@spip_sqlite_select
1107 function spip_sqlite_select($select, $from, $where = '', $groupby = '', $orderby = '', $limit = '', $having = '', $serveur = '', $requeter = true){
1108
1109 // version() n'est pas connu de sqlite
1110 $select = str_replace('version()', 'sqlite_version()', $select);
1111
1112 // recomposer from
1113 $from = (!is_array($from) ? $from : _sqlite_calculer_select_as($from));
1114
1115 $query =
1116 _sqlite_calculer_expression('SELECT', $select, ', ')
1117 ._sqlite_calculer_expression('FROM', $from, ', ')
1118 ._sqlite_calculer_expression('WHERE', $where)
1119 ._sqlite_calculer_expression('GROUP BY', $groupby, ',')
1120 ._sqlite_calculer_expression('HAVING', $having)
1121 .($orderby ? ("\nORDER BY "._sqlite_calculer_order($orderby)) : '')
1122 .($limit ? "\nLIMIT $limit" : '');
1123
1124 // dans un select, on doit renvoyer la requĂȘte en cas d'erreur
1125 $res = spip_sqlite_query($query, $serveur, $requeter);
1126 // texte de la requete demande ?
1127 if (!$requeter) return $res;
1128 // erreur survenue ?
1129 if ($res === false) {
1130 return spip_sqlite::traduire_requete($query, $serveur);
1131 }
1132 return $res;
1133 }
1134
1135
1136 /**
1137 * Selectionne un fichier de base de donnees
1138 *
1139 * @param string $nom
1140 * Nom de la base a utiliser
1141 * @param string $serveur
1142 * Nom du connecteur
1143 * @param bool $requeter
1144 * Inutilise
1145 *
1146 * @return bool|string
1147 * Nom de la base en cas de success.
1148 * False en cas d'erreur.
1149 **/
1150 function spip_sqlite_selectdb($db, $serveur = '', $requeter = true){
1151 _sqlite_init();
1152
1153 // interdire la creation d'une nouvelle base,
1154 // sauf si on est dans l'installation
1155 if (!is_file($f = _DIR_DB.$db.'.sqlite')
1156 && (!defined('_ECRIRE_INSTALL') || !_ECRIRE_INSTALL)){
1157 spip_log("Il est interdit de creer la base $db", 'sqlite.'._LOG_HS);
1158 return false;
1159 }
1160
1161 // se connecter a la base indiquee
1162 // avec les identifiants connus
1163 $index = $serveur ? $serveur : 0;
1164
1165 if ($link = spip_connect_db('', '', '', '', '@selectdb@'.$db, $serveur, '', '')){
1166 if (($db==$link['db']) && $GLOBALS['connexions'][$index] = $link)
1167 return $db;
1168 } else {
1169 spip_log("Impossible de selectionner la base $db", 'sqlite.'._LOG_HS);
1170 return false;
1171 }
1172
1173 }
1174
1175
1176 // http://doc.spip.org/@spip_sqlite_set_charset
1177 function spip_sqlite_set_charset($charset, $serveur = '', $requeter = true){
1178 # spip_log("Gestion charset sql a ecrire : "."SET NAMES "._q($charset), 'sqlite.'._LOG_ERREUR);
1179 # return spip_sqlite_query("SET NAMES ". spip_sqlite_quote($charset), $serveur); //<-- Passe pas !
1180 }
1181
1182
1183 /**
1184 * Retourne une ressource de la liste des tables de la base de données
1185 *
1186 * @param string $match
1187 * Filtre sur tables à récupérer
1188 * @param string $serveur
1189 * Connecteur de la base
1190 * @param bool $requeter
1191 * true pour Ă©xecuter la requĂȘte
1192 * false pour retourner le texte de la requĂȘte.
1193 * @return ressource
1194 * Ressource Ă  utiliser avec sql_fetch()
1195 **/
1196 function spip_sqlite_showbase($match, $serveur = '', $requeter = true){
1197 // type est le type d'entrée : table / index / view
1198 // on ne retourne que les tables (?) et non les vues...
1199 # ESCAPE non supporte par les versions sqlite <3
1200 # return spip_sqlite_query("SELECT name FROM sqlite_master WHERE type='table' AND tbl_name LIKE "._q($match)." ESCAPE '\'", $serveur, $requeter);
1201 $match = preg_quote($match);
1202 $match = str_replace("\\\_", "[[TIRETBAS]]", $match);
1203 $match = str_replace("\\\%", "[[POURCENT]]", $match);
1204 $match = str_replace("_", ".", $match);
1205 $match = str_replace("%", ".*", $match);
1206 $match = str_replace("[[TIRETBAS]]", "_", $match);
1207 $match = str_replace("[[POURCENT]]", "%", $match);
1208 $match = "^$match$";
1209 return spip_sqlite_query("SELECT name FROM sqlite_master WHERE type='table' AND tbl_name REGEXP "._q($match), $serveur, $requeter);
1210 }
1211
1212
1213 // http://doc.spip.org/@spip_sqlite_showtable
1214 function spip_sqlite_showtable($nom_table, $serveur = '', $requeter = true){
1215 $query =
1216 'SELECT sql, type FROM'
1217 .' (SELECT * FROM sqlite_master UNION ALL'
1218 .' SELECT * FROM sqlite_temp_master)'
1219 ." WHERE tbl_name LIKE '$nom_table'"
1220 ." AND type!='meta' AND sql NOT NULL AND name NOT LIKE 'sqlite_%'"
1221 .' ORDER BY substr(type,2,1), name';
1222
1223 $a = spip_sqlite_query($query, $serveur, $requeter);
1224 if (!$a) return "";
1225 if (!$requeter) return $a;
1226 if (!($a = spip_sqlite_fetch($a, null, $serveur))) return "";
1227 $vue = ($a['type']=='view'); // table | vue
1228
1229 // c'est une table
1230 // il faut parser le create
1231 if (!$vue){
1232 if (!preg_match("/^[^(),]*\((([^()]*(\([^()]*\))?[^()]*)*)\)[^()]*$/", array_shift($a), $r))
1233 return "";
1234 else {
1235 $desc = $r[1];
1236 // extraction d'une KEY Ă©ventuelle en prenant garde de ne pas
1237 // relever un champ dont le nom contient KEY (ex. ID_WHISKEY)
1238 if (preg_match("/^(.*?),([^,]*KEY[ (].*)$/s", $desc, $r)){
1239 $namedkeys = $r[2];
1240 $desc = $r[1];
1241 }
1242 else
1243 $namedkeys = "";
1244
1245 $fields = array();
1246 $keys = array();
1247
1248 // enlever les contenus des valeurs DEFAULT 'xxx' qui pourraient perturber
1249 // par exemple s'il contiennent une virgule.
1250 // /!\ cela peut aussi echapper le nom des champs si la table a eu des operations avec SQLite Manager !
1251 list($desc, $echaps) = query_echappe_textes($desc);
1252
1253 // separer toutes les descriptions de champs, separes par des virgules
1254 # /!\ explode peut exploser aussi DECIMAL(10,2) !
1255 $k_precedent = null;
1256 foreach (explode(",", $desc) as $v){
1257
1258 preg_match("/^\s*([^\s]+)\s+(.*)/", $v, $r);
1259 // Les cles de champs peuvent etre entourees
1260 // de guillements doubles " , simples ', graves ` ou de crochets [ ], ou rien.
1261 // http://www.sqlite.org/lang_keywords.html
1262 $k = strtolower(query_reinjecte_textes($r[1], $echaps)); // champ, "champ", [champ]...
1263 if ($char = strpbrk($k[0], '\'"[`')) {
1264 $k = trim($k, $char);
1265 if ($char == '[') $k = rtrim($k, ']');
1266 }
1267 $def = query_reinjecte_textes($r[2], $echaps); // valeur du champ
1268
1269 # rustine pour DECIMAL(10,2)
1270 if (false !== strpos($k, ')')) {
1271 $fields[$k_precedent] .= ',' . $k . ' ' . $def;
1272 continue;
1273 }
1274
1275 $fields[$k] = $def;
1276 $k_precedent = $k;
1277
1278 // la primary key peut etre dans une des descriptions de champs
1279 // et non en fin de table, cas encore decouvert avec Sqlite Manager
1280 if (stripos($r[2], 'PRIMARY KEY') !== false) {
1281 $keys['PRIMARY KEY'] = $k;
1282 }
1283 }
1284 // key inclues dans la requete
1285 foreach (preg_split('/\)\s*,?/', $namedkeys) as $v){
1286 if (preg_match("/^\s*([^(]*)\((.*)$/", $v, $r)){
1287 $k = str_replace("`", '', trim($r[1]));
1288 $t = trim(strtolower(str_replace("`", '', $r[2])), '"');
1289 if ($k && !isset($keys[$k])) $keys[$k] = $t; else $keys[] = $t;
1290 }
1291 }
1292 // sinon ajouter les key index
1293 $query =
1294 'SELECT name,sql FROM'
1295 .' (SELECT * FROM sqlite_master UNION ALL'
1296 .' SELECT * FROM sqlite_temp_master)'
1297 ." WHERE tbl_name LIKE '$nom_table'"
1298 ." AND type='index' AND name NOT LIKE 'sqlite_%'"
1299 .'ORDER BY substr(type,2,1), name';
1300 $a = spip_sqlite_query($query, $serveur, $requeter);
1301 while ($r = spip_sqlite_fetch($a, null, $serveur)){
1302 $key = str_replace($nom_table.'_', '', $r['name']); // enlever le nom de la table ajoute a l'index
1303 $colonnes = preg_replace(',.*\((.*)\).*,', '$1', $r['sql']);
1304 $keys['KEY '.$key] = $colonnes;
1305 }
1306 }
1307 // c'est une vue, on liste les champs disponibles simplement
1308 } else {
1309 if ($res = sql_fetsel('*', $nom_table, '', '', '', '1', '', $serveur)){ // limit 1
1310 $fields = array();
1311 foreach ($res as $c => $v) $fields[$c] = '';
1312 $keys = array();
1313 } else {
1314 return "";
1315 }
1316 }
1317 return array('field' => $fields, 'key' => $keys);
1318
1319 }
1320
1321
1322 // http://doc.spip.org/@spip_sqlite_update
1323 function spip_sqlite_update($table, $champs, $where = '', $desc = '', $serveur = '', $requeter = true){
1324 // recherche de champs 'timestamp' pour mise a jour auto de ceux-ci
1325 $champs = _sqlite_ajouter_champs_timestamp($table, $champs, $desc, $serveur);
1326
1327 $set = array();
1328 foreach ($champs as $champ => $val)
1329 $set[] = $champ."=$val";
1330 if (!empty($set))
1331 return spip_sqlite_query(
1332 _sqlite_calculer_expression('UPDATE', $table, ',')
1333 ._sqlite_calculer_expression('SET', $set, ',')
1334 ._sqlite_calculer_expression('WHERE', $where),
1335 $serveur, $requeter);
1336 }
1337
1338
1339 // http://doc.spip.org/@spip_sqlite_updateq
1340 function spip_sqlite_updateq($table, $champs, $where = '', $desc = array(), $serveur = '', $requeter = true){
1341
1342 if (!$champs) return;
1343 if (!$desc) $desc = description_table($table, $serveur);
1344 if (!$desc) die("$table insertion sans description");
1345 $fields = $desc['field'];
1346
1347 // recherche de champs 'timestamp' pour mise a jour auto de ceux-ci
1348 $champs = _sqlite_ajouter_champs_timestamp($table, $champs, $desc, $serveur);
1349
1350 $set = array();
1351 foreach ($champs as $champ => $val){
1352 $set[] = $champ.'='._sqlite_calculer_cite($val, $fields[$champ]);
1353 }
1354 return spip_sqlite_query(
1355 _sqlite_calculer_expression('UPDATE', $table, ',')
1356 ._sqlite_calculer_expression('SET', $set, ',')
1357 ._sqlite_calculer_expression('WHERE', $where),
1358 $serveur, $requeter);
1359 }
1360
1361
1362 /*
1363 *
1364 * Ensuite les fonctions non abstraites
1365 * crees pour l'occasion de sqlite
1366 *
1367 */
1368
1369
1370 /**
1371 * fonction pour la premiere connexion a un serveur SQLite
1372 * http://doc.spip.org/@_sqlite_init
1373 *
1374 * @return void
1375 */
1376 function _sqlite_init(){
1377 if (!defined('_DIR_DB')) define('_DIR_DB', _DIR_ETC.'bases/');
1378 if (!defined('_SQLITE_CHMOD')) define('_SQLITE_CHMOD', _SPIP_CHMOD);
1379
1380 if (!is_dir($d = _DIR_DB)){
1381 include_spip('inc/flock');
1382 sous_repertoire($d);
1383 }
1384 }
1385
1386
1387 /**
1388 * teste la version sqlite du link en cours
1389 * http://doc.spip.org/@_sqlite_is_version
1390 *
1391 * @param string $version
1392 * @param string $link
1393 * @param string $serveur
1394 * @param bool $requeter
1395 * @return bool|int
1396 */
1397 function _sqlite_is_version($version = '', $link = '', $serveur = '', $requeter = true){
1398 if ($link==='') $link = _sqlite_link($serveur);
1399 if (!$link) return false;
1400 if ($link instanceof PDO){
1401 $v = 3;
1402 } else {
1403 $v = 2;
1404 }
1405
1406 if (!$version) return $v;
1407 return ($version==$v);
1408 }
1409
1410
1411 /**
1412 * retrouver un link
1413 * http://doc.spip.org/@_sqlite_link
1414 *
1415 * @param string $serveur
1416 * @return
1417 */
1418 function _sqlite_link($serveur = ''){
1419 $link = &$GLOBALS['connexions'][$serveur ? $serveur : 0]['link'];
1420 return $link;
1421 }
1422
1423
1424 /* ordre alphabetique pour les autres */
1425
1426
1427 /**
1428 * renvoie les bons echappements (pas sur les fonctions now())
1429 * http://doc.spip.org/@_sqlite_calculer_cite
1430 *
1431 * @param string|array|number $v
1432 * @param string $type
1433 * @return string|array|number
1434 */
1435 function _sqlite_calculer_cite($v, $type){
1436 if ($type){
1437 if(is_null($v)
1438 AND stripos($type,"NOT NULL")===false) return 'NULL'; // null php se traduit en NULL SQL
1439
1440 if (sql_test_date($type) AND preg_match('/^\w+\(/', $v))
1441 return $v;
1442 if (sql_test_int($type)){
1443 if (is_numeric($v))
1444 return $v;
1445 elseif (ctype_xdigit(substr($v, 2)) AND strncmp($v, '0x', 2)==0)
1446 return hexdec(substr($v, 2));
1447 else
1448 return intval($v);
1449 }
1450 }
1451 else {
1452 // si on ne connait pas le type on le deduit de $v autant que possible
1453 if (is_numeric($v))
1454 return strval($v);
1455 }
1456
1457 if (function_exists('sqlite_escape_string')){
1458 return "'".sqlite_escape_string($v)."'";
1459 }
1460
1461 // trouver un link sqlite3 pour faire l'echappement
1462 foreach ($GLOBALS['connexions'] as $s){
1463 if (_sqlite_is_version(3, $l = $s['link'])){
1464 return $l->quote($v);
1465 }
1466 }
1467
1468 // echapper les ' en ''
1469 spip_log("Pas de methode sqlite_escape_string ni ->quote pour echapper","sqlite."._LOG_INFO_IMPORTANTE);
1470 return ("'" . str_replace("'","''",$v) . "'");
1471 }
1472
1473
1474 /**
1475 * renvoie grosso modo "$expression join($join, $v)"
1476 * http://doc.spip.org/@_sqlite_calculer_expression
1477 *
1478 * @param $expression
1479 * @param $v
1480 * @param string $join
1481 * @return string
1482 */
1483 function _sqlite_calculer_expression($expression, $v, $join = 'AND'){
1484 if (empty($v))
1485 return '';
1486
1487 $exp = "\n$expression ";
1488
1489 if (!is_array($v)){
1490 return $exp.$v;
1491 } else {
1492 if (strtoupper($join)==='AND')
1493 return $exp.join("\n\t$join ", array_map('_sqlite_calculer_where', $v));
1494 else
1495 return $exp.join($join, $v);
1496 }
1497 }
1498
1499
1500 /**
1501 * pour conversion 0+x ? (pas la peine en sqlite)
1502 * http://doc.spip.org/@_sqlite_calculer_order
1503 *
1504 * @param $orderby
1505 * @return string
1506 */
1507 function _sqlite_calculer_order($orderby){
1508 return (is_array($orderby)) ? join(", ", $orderby) : $orderby;
1509 }
1510
1511
1512 // renvoie des 'nom AS alias'
1513 // http://doc.spip.org/@_sqlite_calculer_select_as
1514 function _sqlite_calculer_select_as($args){
1515 $res = '';
1516 foreach ($args as $k => $v){
1517 if (substr($k, -1)=='@'){
1518 // c'est une jointure qui se refere au from precedent
1519 // pas de virgule
1520 $res .= ' '.$v;
1521 }
1522 else {
1523 if (!is_numeric($k)){
1524 $p = strpos($v, " ");
1525 if ($p)
1526 $v = substr($v, 0, $p)." AS '$k'".substr($v, $p);
1527 else $v .= " AS '$k'";
1528 }
1529 $res .= ', '.$v;
1530 }
1531 }
1532 return substr($res, 2);
1533 }
1534
1535
1536 /**
1537 * renvoie les bonnes parentheses pour des where imbriquees
1538 * http://doc.spip.org/@_sqlite_calculer_where
1539 *
1540 * @param $v
1541 * @return array|mixed|string
1542 */
1543 function _sqlite_calculer_where($v){
1544 if (!is_array($v))
1545 return $v;
1546
1547 $op = array_shift($v);
1548 if (!($n = count($v)))
1549 return $op;
1550 else {
1551 $arg = _sqlite_calculer_where(array_shift($v));
1552 if ($n==1){
1553 return "$op($arg)";
1554 } else {
1555 $arg2 = _sqlite_calculer_where(array_shift($v));
1556 if ($n==2){
1557 return "($arg $op $arg2)";
1558 } else return "($arg $op ($arg2) : $v[0])";
1559 }
1560 }
1561 }
1562
1563
1564 /**
1565 * Charger les modules sqlite (si possible) (juste la version demandee),
1566 * ou, si aucune version, renvoie les versions sqlite dispo
1567 * sur ce serveur dans un array
1568 *
1569 * http://doc.spip.org/@_sqlite_charger_version
1570 *
1571 * @param string $version
1572 * @return array|bool
1573 */
1574 function _sqlite_charger_version($version = ''){
1575 $versions = array();
1576
1577 // version 2
1578 if (!$version || $version==2){
1579 if (charger_php_extension('sqlite')){
1580 $versions[] = 2;
1581 }
1582 }
1583
1584 // version 3
1585 if (!$version || $version==3){
1586 if (charger_php_extension('pdo') && charger_php_extension('pdo_sqlite')){
1587 $versions[] = 3;
1588 }
1589 }
1590 if ($version) return in_array($version, $versions);
1591 return $versions;
1592 }
1593
1594
1595 /**
1596 * Gestion des requetes ALTER non reconnues de SQLite :
1597 * ALTER TABLE table DROP column
1598 * ALTER TABLE table CHANGE [COLUMN] columnA columnB definition
1599 * ALTER TABLE table MODIFY column definition
1600 * ALTER TABLE table ADD|DROP PRIMARY KEY
1601 *
1602 * (MODIFY transforme en CHANGE columnA columnA) par spip_sqlite_alter()
1603 *
1604 * 1) creer une table B avec le nouveau format souhaite
1605 * 2) copier la table d'origine A vers B
1606 * 3) supprimer la table A
1607 * 4) renommer la table B en A
1608 * 5) remettre les index (qui sont supprimes avec la table A)
1609 *
1610 * http://doc.spip.org/@_sqlite_modifier_table
1611 *
1612 * @param string/array $table : nom_table, array(nom_table=>nom_futur)
1613 * @param string/array $col : nom_colonne, array(nom_colonne=>nom_futur)
1614 * @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', ...))
1615 * @param string $serveur : nom de la connexion sql en cours
1616 *
1617 */
1618 function _sqlite_modifier_table($table, $colonne, $opt = array(), $serveur = ''){
1619
1620 if (is_array($table)){
1621 reset($table);
1622 list($table_origine,$table_destination) = each($table);
1623 } else {
1624 $table_origine = $table_destination = $table;
1625 }
1626 // ne prend actuellement qu'un changement
1627 // mais pourra etre adapte pour changer plus qu'une colonne a la fois
1628 if (is_array($colonne)){
1629 reset($colonne);
1630 list($colonne_origine,$colonne_destination) = each($colonne);
1631 } else {
1632 $colonne_origine = $colonne_destination = $colonne;
1633 }
1634 if (!isset($opt['field'])) $opt['field'] = array();
1635 if (!isset($opt['key'])) $opt['key'] = array();
1636
1637 // si les noms de tables sont differents, pas besoin de table temporaire
1638 // on prendra directement le nom de la future table
1639 $meme_table = ($table_origine==$table_destination);
1640
1641 $def_origine = sql_showtable($table_origine, false, $serveur);
1642 if (!$def_origine OR !isset($def_origine['field'])){
1643 spip_log("Alter table impossible sur $table_origine : table non trouvee",'sqlite'._LOG_ERREUR);
1644 return false;
1645 }
1646
1647
1648 $table_tmp = $table_origine.'_tmp';
1649
1650 // 1) creer une table temporaire avec les modifications
1651 // - DROP : suppression de la colonne
1652 // - CHANGE : modification de la colonne
1653 // (foreach pour conserver l'ordre des champs)
1654
1655 // field
1656 $fields = array();
1657 // pour le INSERT INTO plus loin
1658 // stocker la correspondance nouvelles->anciennes colonnes
1659 $fields_correspondances = array();
1660 foreach ($def_origine['field'] as $c => $d){
1661
1662 if ($colonne_origine && ($c==$colonne_origine)){
1663 // si pas DROP
1664 if ($colonne_destination){
1665 $fields[$colonne_destination] = $opt['field'][$colonne_destination];
1666 $fields_correspondances[$colonne_destination] = $c;
1667 }
1668 } else {
1669 $fields[$c] = $d;
1670 $fields_correspondances[$c] = $c;
1671 }
1672 }
1673 // cas de ADD sqlite2 (ajout du champ en fin de table):
1674 if (!$colonne_origine && $colonne_destination){
1675 $fields[$colonne_destination] = $opt['field'][$colonne_destination];
1676 }
1677
1678 // key...
1679 $keys = array();
1680 foreach ($def_origine['key'] as $c => $d){
1681 $c = str_replace($colonne_origine, $colonne_destination, $c);
1682 $d = str_replace($colonne_origine, $colonne_destination, $d);
1683 // seulement si on ne supprime pas la colonne !
1684 if ($d)
1685 $keys[$c] = $d;
1686 }
1687
1688 // autres keys, on merge
1689 $keys = array_merge($keys, $opt['key']);
1690 $queries = array();
1691
1692 // copier dans destination (si differente de origine), sinon tmp
1693 $table_copie = ($meme_table) ? $table_tmp : $table_destination;
1694 $autoinc = (isset($keys['PRIMARY KEY'])
1695 AND stripos($keys['PRIMARY KEY'],',')===false
1696 AND stripos($fields[$keys['PRIMARY KEY']],'default')===false);
1697
1698 if ($q = _sqlite_requete_create(
1699 $table_copie,
1700 $fields,
1701 $keys,
1702 $autoinc,
1703 $temporary = false,
1704 $ifnotexists = true,
1705 $serveur)){
1706 $queries[] = $q;
1707 }
1708
1709
1710 // 2) y copier les champs qui vont bien
1711 $champs_dest = join(', ', array_keys($fields_correspondances));
1712 $champs_ori = join(', ', $fields_correspondances);
1713 $queries[] = "INSERT INTO $table_copie ($champs_dest) SELECT $champs_ori FROM $table_origine";
1714
1715 // 3) supprimer la table d'origine
1716 $queries[] = "DROP TABLE $table_origine";
1717
1718 // 4) renommer la table temporaire
1719 // avec le nom de la table destination
1720 // si necessaire
1721 if ($meme_table){
1722 if (_sqlite_is_version(3, '', $serveur)){
1723 $queries[] = "ALTER TABLE $table_copie RENAME TO $table_destination";
1724 } else {
1725 $queries[] = _sqlite_requete_create(
1726 $table_destination,
1727 $fields,
1728 $keys,
1729 $autoinc,
1730 $temporary = false,
1731 $ifnotexists = false, // la table existe puisqu'on est dans une transaction
1732 $serveur);
1733 $queries[] = "INSERT INTO $table_destination SELECT * FROM $table_copie";
1734 $queries[] = "DROP TABLE $table_copie";
1735 }
1736 }
1737
1738 // 5) remettre les index !
1739 foreach ($keys as $k => $v){
1740 if ($k=='PRIMARY KEY'){
1741 }
1742 else {
1743 // enlever KEY
1744 $k = substr($k, 4);
1745 $queries[] = "CREATE INDEX $table_destination"."_$k ON $table_destination ($v)";
1746 }
1747 }
1748
1749
1750 if (count($queries)){
1751 spip_sqlite::demarrer_transaction($serveur);
1752 // il faut les faire une par une car $query = join('; ', $queries).";"; ne fonctionne pas
1753 foreach ($queries as $q){
1754 if (!spip_sqlite::executer_requete($q, $serveur)){
1755 spip_log(_LOG_GRAVITE_ERREUR, "SQLite : ALTER TABLE table :"
1756 ." Erreur a l'execution de la requete : $q", 'sqlite');
1757 spip_sqlite::annuler_transaction($serveur);
1758 return false;
1759 }
1760 }
1761 spip_sqlite::finir_transaction($serveur);
1762 }
1763
1764 return true;
1765 }
1766
1767
1768 /**
1769 * Nom des fonctions
1770 * http://doc.spip.org/@_sqlite_ref_fonctions
1771 *
1772 * @return array
1773 */
1774 function _sqlite_ref_fonctions(){
1775 $fonctions = array(
1776 'alter' => 'spip_sqlite_alter',
1777 'count' => 'spip_sqlite_count',
1778 'countsel' => 'spip_sqlite_countsel',
1779 'create' => 'spip_sqlite_create',
1780 'create_base' => 'spip_sqlite_create_base',
1781 'create_view' => 'spip_sqlite_create_view',
1782 'date_proche' => 'spip_sqlite_date_proche',
1783 'delete' => 'spip_sqlite_delete',
1784 'drop_table' => 'spip_sqlite_drop_table',
1785 'drop_view' => 'spip_sqlite_drop_view',
1786 'errno' => 'spip_sqlite_errno',
1787 'error' => 'spip_sqlite_error',
1788 'explain' => 'spip_sqlite_explain',
1789 'fetch' => 'spip_sqlite_fetch',
1790 'seek' => 'spip_sqlite_seek',
1791 'free' => 'spip_sqlite_free',
1792 'hex' => 'spip_sqlite_hex',
1793 'in' => 'spip_sqlite_in',
1794 'insert' => 'spip_sqlite_insert',
1795 'insertq' => 'spip_sqlite_insertq',
1796 'insertq_multi' => 'spip_sqlite_insertq_multi',
1797 'listdbs' => 'spip_sqlite_listdbs',
1798 'multi' => 'spip_sqlite_multi',
1799 'optimize' => 'spip_sqlite_optimize',
1800 'query' => 'spip_sqlite_query',
1801 'quote' => 'spip_sqlite_quote',
1802 'replace' => 'spip_sqlite_replace',
1803 'replace_multi' => 'spip_sqlite_replace_multi',
1804 'select' => 'spip_sqlite_select',
1805 'selectdb' => 'spip_sqlite_selectdb',
1806 'set_charset' => 'spip_sqlite_set_charset',
1807 'get_charset' => 'spip_sqlite_get_charset',
1808 'showbase' => 'spip_sqlite_showbase',
1809 'showtable' => 'spip_sqlite_showtable',
1810 'update' => 'spip_sqlite_update',
1811 'updateq' => 'spip_sqlite_updateq',
1812 'preferer_transaction' => 'spip_sqlite_preferer_transaction',
1813 'demarrer_transaction' => 'spip_sqlite_demarrer_transaction',
1814 'terminer_transaction' => 'spip_sqlite_terminer_transaction',
1815 );
1816
1817 // association de chaque nom http d'un charset aux couples sqlite
1818 // SQLite supporte utf-8 et utf-16 uniquement.
1819 $charsets = array(
1820 'utf-8' => array('charset' => 'utf8', 'collation' => 'utf8_general_ci'),
1821 //'utf-16be'=>array('charset'=>'utf16be','collation'=>'UTF-16BE'),// aucune idee de quoi il faut remplir dans es champs la
1822 //'utf-16le'=>array('charset'=>'utf16le','collation'=>'UTF-16LE')
1823 );
1824
1825 $fonctions['charsets'] = $charsets;
1826
1827 return $fonctions;
1828 }
1829
1830
1831 /**
1832 * $query est une requete ou une liste de champs
1833 * http://doc.spip.org/@_sqlite_remplacements_definitions_table
1834 *
1835 * @param $query
1836 * @param bool $autoinc
1837 * @return mixed
1838 */
1839 function _sqlite_remplacements_definitions_table($query, $autoinc = false){
1840 // quelques remplacements
1841 $num = "(\s*\([0-9]*\))?";
1842 $enum = "(\s*\([^\)]*\))?";
1843
1844 $remplace = array(
1845 '/enum'.$enum.'/is' => 'VARCHAR(255)',
1846 '/COLLATE \w+_bin/is' => 'COLLATE BINARY',
1847 '/COLLATE \w+_ci/is' => 'COLLATE NOCASE',
1848 '/auto_increment/is' => '',
1849 '/(timestamp .* )ON .*$/is' => '\\1',
1850 '/character set \w+/is' => '',
1851 '/((big|small|medium|tiny)?int(eger)?)'.$num.'\s*unsigned/is' => '\\1 UNSIGNED',
1852 '/(text\s+not\s+null(\s+collate\s+\w+)?)\s*$/is' => "\\1 DEFAULT ''",
1853 '/((char|varchar)'.$num.'\s+not\s+null(\s+collate\s+\w+)?)\s*$/is' => "\\1 DEFAULT ''",
1854 '/(datetime\s+not\s+null)\s*$/is' => "\\1 DEFAULT '0000-00-00 00:00:00'",
1855 '/(date\s+not\s+null)\s*$/is' => "\\1 DEFAULT '0000-00-00'",
1856 );
1857
1858 // pour l'autoincrement, il faut des INTEGER NOT NULL PRIMARY KEY
1859 $remplace_autocinc = array(
1860 '/(big|small|medium|tiny)?int(eger)?'.$num.'/is' => 'INTEGER'
1861 );
1862 // pour les int non autoincrement, il faut un DEFAULT
1863 $remplace_nonautocinc = array(
1864 '/((big|small|medium|tiny)?int(eger)?'.$num.'\s+not\s+null)\s*$/is' => "\\1 DEFAULT 0",
1865 );
1866
1867 if (is_string($query)){
1868 $query = preg_replace(array_keys($remplace), $remplace, $query);
1869 if ($autoinc OR preg_match(',AUTO_INCREMENT,is',$query))
1870 $query = preg_replace(array_keys($remplace_autocinc), $remplace_autocinc, $query);
1871 else{
1872 $query = preg_replace(array_keys($remplace_nonautocinc), $remplace_nonautocinc, $query);
1873 $query = _sqlite_collate_ci($query);
1874 }
1875 }
1876 elseif(is_array($query)){
1877 foreach($query as $k=>$q) {
1878 $ai = ($autoinc?$k==$autoinc:preg_match(',AUTO_INCREMENT,is',$q));
1879 $query[$k] = preg_replace(array_keys($remplace), $remplace, $query[$k]);
1880 if ($ai)
1881 $query[$k] = preg_replace(array_keys($remplace_autocinc), $remplace_autocinc, $query[$k]);
1882 else{
1883 $query[$k] = preg_replace(array_keys($remplace_nonautocinc), $remplace_nonautocinc, $query[$k]);
1884 $query[$k] = _sqlite_collate_ci($query[$k]);
1885 }
1886 }
1887 }
1888 return $query;
1889 }
1890
1891 /**
1892 * Definir la collation d'un champ en fonction de si une collation est deja explicite
1893 * et du par defaut que l'on veut NOCASE
1894 * @param string $champ
1895 * @return string
1896 */
1897 function _sqlite_collate_ci($champ){
1898 if (stripos($champ,"COLLATE")!==false)
1899 return $champ;
1900 if (stripos($champ,"BINARY")!==false)
1901 return str_ireplace("BINARY","COLLATE BINARY",$champ);
1902 if (preg_match(",^(char|varchar|(long|small|medium|tiny)?text),i",$champ))
1903 return $champ . " COLLATE NOCASE";
1904
1905 return $champ;
1906 }
1907
1908
1909 /**
1910 * Creer la requete pour la creation d'une table
1911 * retourne la requete pour utilisation par sql_create() et sql_alter()
1912 *
1913 * http://doc.spip.org/@_sqlite_requete_create
1914 *
1915 * @param $nom
1916 * @param $champs
1917 * @param $cles
1918 * @param bool $autoinc
1919 * @param bool $temporary
1920 * @param bool $_ifnotexists
1921 * @param string $serveur
1922 * @param bool $requeter
1923 * @return bool|string
1924 */
1925 function _sqlite_requete_create($nom, $champs, $cles, $autoinc = false, $temporary = false, $_ifnotexists = true, $serveur = '', $requeter = true){
1926 $query = $keys = $s = $p = '';
1927
1928 // certains plugins declarent les tables (permet leur inclusion dans le dump)
1929 // sans les renseigner (laisse le compilo recuperer la description)
1930 if (!is_array($champs) || !is_array($cles))
1931 return;
1932
1933 // sqlite ne gere pas KEY tout court dans une requete CREATE TABLE
1934 // il faut passer par des create index
1935 // Il gere par contre primary key !
1936 // Soit la PK est definie dans les cles, soit dans un champs
1937 $c = ""; // le champ de cle primaire
1938 if (!isset($cles[$pk = "PRIMARY KEY"]) OR !$c = $cles[$pk]){
1939 foreach ($champs as $k => $v){
1940 if (false!==stripos($v, $pk)){
1941 $c = $k;
1942 // on n'en a plus besoin dans field, vu que defini dans key
1943 $champs[$k] = preg_replace("/$pk/is", '', $champs[$k]);
1944 break;
1945 }
1946 }
1947 }
1948 if ($c) $keys = "\n\t\t$pk ($c)";
1949 // Pas de DEFAULT 0 sur les cles primaires en auto-increment
1950 if (isset($champs[$c])
1951 AND stripos($champs[$c],"default 0")!==false){
1952 $champs[$c] = trim(str_ireplace("default 0","",$champs[$c]));
1953 }
1954
1955 $champs = _sqlite_remplacements_definitions_table($champs, $autoinc?$c:false);
1956 foreach ($champs as $k => $v){
1957 $query .= "$s\n\t\t$k $v";
1958 $s = ",";
1959 }
1960
1961 $ifnotexists = "";
1962 if ($_ifnotexists){
1963
1964 $version = spip_sqlite_fetch(spip_sqlite_query("select sqlite_version() AS sqlite_version",$serveur),'',$serveur);
1965 if (!function_exists('spip_version_compare')) include_spip('plugins/installer');
1966
1967 if ($version AND spip_version_compare($version['sqlite_version'],'3.3.0','>=')) {
1968 $ifnotexists = ' IF NOT EXISTS';
1969 } else {
1970 /* simuler le IF EXISTS - version 2 et sqlite < 3.3a */
1971 $a = spip_sqlite_showtable($table, $serveur);
1972 if (isset($a['key']['KEY '.$nom])) return true;
1973 }
1974
1975 }
1976
1977 $temporary = $temporary ? ' TEMPORARY' : '';
1978 $q = "CREATE$temporary TABLE$ifnotexists $nom ($query".($keys ? ",$keys" : '').")\n";
1979
1980 return $q;
1981 }
1982
1983
1984 /**
1985 * Retrouver les champs 'timestamp'
1986 * pour les ajouter aux 'insert' ou 'replace'
1987 * afin de simuler le fonctionnement de mysql
1988 *
1989 * stocke le resultat pour ne pas faire
1990 * de requetes showtable intempestives
1991 *
1992 * http://doc.spip.org/@_sqlite_ajouter_champs_timestamp
1993 *
1994 * @param $table
1995 * @param $couples
1996 * @param string $desc
1997 * @param string $serveur
1998 * @return
1999 */
2000 function _sqlite_ajouter_champs_timestamp($table, $couples, $desc = '', $serveur = ''){
2001 static $tables = array();
2002
2003 if (!isset($tables[$table])){
2004
2005 if (!$desc){
2006 $trouver_table = charger_fonction('trouver_table', 'base');
2007 $desc = $trouver_table($table, $serveur);
2008 // si pas de description, on ne fait rien, ou on die() ?
2009 if (!$desc) return $couples;
2010 }
2011
2012 // recherche des champs avec simplement 'TIMESTAMP'
2013 // cependant, il faudra peut etre etendre
2014 // avec la gestion de DEFAULT et ON UPDATE
2015 // mais ceux-ci ne sont pas utilises dans le core
2016 $tables[$table] = array();
2017
2018 foreach ($desc['field'] as $k => $v){
2019 if (strpos(strtolower(ltrim($v)), 'timestamp')===0)
2020 $tables[$table][$k] = "datetime('now')";
2021 }
2022 }
2023
2024 // ajout des champs type 'timestamp' absents
2025 return array_merge($tables[$table],$couples);
2026 }
2027
2028
2029 /**
2030 * renvoyer la liste des versions sqlite disponibles
2031 * sur le serveur
2032 * http://doc.spip.org/@spip_versions_sqlite
2033 *
2034 * @return array|bool
2035 */
2036 function spip_versions_sqlite(){
2037 return _sqlite_charger_version();
2038 }
2039
2040
2041 class spip_sqlite {
2042 static $requeteurs = array();
2043 static $transaction_en_cours = array();
2044
2045 function spip_sqlite(){}
2046
2047 /**
2048 * Retourne une unique instance du requĂȘteur
2049 *
2050 * Retourne une instance unique du requĂȘteur pour une connexion SQLite
2051 * donnée
2052 *
2053 * @param string $serveur
2054 * Nom du connecteur
2055 * @return sqlite_requeteur
2056 * Instance unique du requĂȘteur
2057 **/
2058 static function requeteur($serveur){
2059 if (!isset(spip_sqlite::$requeteurs[$serveur]))
2060 spip_sqlite::$requeteurs[$serveur] = new sqlite_requeteur($serveur);
2061 return spip_sqlite::$requeteurs[$serveur];
2062 }
2063
2064 static function traduire_requete($query, $serveur){
2065 $requeteur = spip_sqlite::requeteur($serveur);
2066 $traducteur = new sqlite_traducteur($query, $requeteur->prefixe,$requeteur->sqlite_version);
2067 return $traducteur->traduire_requete();
2068 }
2069
2070 static function demarrer_transaction($serveur){
2071 spip_sqlite::executer_requete("BEGIN TRANSACTION",$serveur);
2072 spip_sqlite::$transaction_en_cours[$serveur] = true;
2073 }
2074
2075 static function executer_requete($query, $serveur, $tracer=null){
2076 $requeteur = spip_sqlite::requeteur($serveur);
2077 return $requeteur->executer_requete($query, $tracer);
2078 }
2079
2080 static function last_insert_id($serveur){
2081 $requeteur = spip_sqlite::requeteur($serveur);
2082 return $requeteur->last_insert_id($serveur);
2083 }
2084
2085 static function annuler_transaction($serveur){
2086 spip_sqlite::executer_requete("ROLLBACK",$serveur);
2087 spip_sqlite::$transaction_en_cours[$serveur] = false;
2088 }
2089
2090 static function finir_transaction($serveur){
2091 // si pas de transaction en cours, ne rien faire et le dire
2092 if (!isset (spip_sqlite::$transaction_en_cours[$serveur])
2093 OR spip_sqlite::$transaction_en_cours[$serveur]==false)
2094 return false;
2095 // sinon fermer la transaction et retourner true
2096 spip_sqlite::executer_requete("COMMIT",$serveur);
2097 spip_sqlite::$transaction_en_cours[$serveur] = false;
2098 return true;
2099 }
2100 }
2101
2102 /*
2103 * Classe pour partager les lancements de requete
2104 * instanciee une fois par $serveur
2105 * - peut corriger la syntaxe des requetes pour la conformite a sqlite
2106 * - peut tracer les requetes
2107 *
2108 */
2109 class sqlite_requeteur {
2110 var $query = ''; // la requete
2111 var $serveur = ''; // le serveur
2112 var $link = ''; // le link (ressource) sqlite
2113 var $prefixe = ''; // le prefixe des tables
2114 var $db = ''; // le nom de la base
2115 var $tracer = false; // doit-on tracer les requetes (var_profile)
2116
2117 var $sqlite_version = ''; // Version de sqlite (2 ou 3)
2118
2119 /**
2120 * constructeur
2121 * http://doc.spip.org/@sqlite_traiter_requete
2122 *
2123 * @param $query
2124 * @param string $serveur
2125 * @return bool
2126 */
2127 function sqlite_requeteur($serveur = ''){
2128 _sqlite_init();
2129 $this->serveur = strtolower($serveur);
2130
2131 if (!($this->link = _sqlite_link($this->serveur)) && (!defined('_ECRIRE_INSTALL') || !_ECRIRE_INSTALL)){
2132 spip_log("Aucune connexion sqlite (link)", 'sqlite.'._LOG_ERREUR);
2133 return false;
2134 }
2135
2136 $this->sqlite_version = _sqlite_is_version('', $this->link);
2137
2138 $this->prefixe = $GLOBALS['connexions'][$this->serveur ? $this->serveur : 0]['prefixe'];
2139 $this->db = $GLOBALS['connexions'][$this->serveur ? $this->serveur : 0]['db'];
2140
2141 // tracage des requetes ?
2142 $this->tracer = (isset($_GET['var_profile']) && $_GET['var_profile']);
2143 }
2144
2145 /**
2146 * lancer la requete $query,
2147 * faire le tracage si demande
2148 * http://doc.spip.org/@executer_requete
2149 *
2150 * @return bool|SQLiteResult
2151 */
2152 function executer_requete($query, $tracer=null){
2153 if (is_null($tracer))
2154 $tracer = $this->tracer;
2155 $err = "";
2156 $t = 0;
2157 if ($tracer){
2158 include_spip('public/tracer');
2159 $t = trace_query_start();
2160 }
2161
2162 # spip_log("requete: $this->serveur >> $query",'sqlite.'._LOG_DEBUG); // boum ? pourquoi ?
2163 if ($this->link){
2164 // memoriser la derniere erreur PHP vue
2165 $e = (function_exists('error_get_last')?error_get_last():"");
2166 // sauver la derniere requete
2167 $GLOBALS['connexions'][$this->serveur ? $this->serveur : 0]['last'] = $query;
2168
2169 if ($this->sqlite_version==3){
2170 $r = $this->link->query($query);
2171 // sauvegarde de la requete (elle y est deja dans $r->queryString)
2172 # $r->spipQueryString = $query;
2173
2174 // comptage : oblige de compter le nombre d'entrees retournees
2175 // par une requete SELECT
2176 // aucune autre solution ne donne le nombre attendu :( !
2177 // particulierement s'il y a des LIMIT dans la requete.
2178 if (strtoupper(substr(ltrim($query), 0, 6))=='SELECT'){
2179 if ($r){
2180 // noter le link et la query pour faire le comptage *si* on en a besoin
2181 $r->spipSqliteRowCount = array($this->link,$query);
2182 }
2183 elseif ($r instanceof PDOStatement) {
2184 $r->spipSqliteRowCount = 0;
2185 }
2186 }
2187 }
2188 else {
2189 $r = sqlite_query($this->link, $query);
2190 }
2191
2192 // loger les warnings/erreurs eventuels de sqlite remontant dans PHP
2193 if ($err = (function_exists('error_get_last')?error_get_last():"") AND $err!=$e){
2194 $err = strip_tags($err['message'])." in ".$err['file']." line ".$err['line'];
2195 spip_log("$err - ".$query, 'sqlite.'._LOG_ERREUR);
2196 }
2197 else $err = "";
2198
2199 }
2200 else {
2201 $r = false;
2202 }
2203
2204 if (spip_sqlite_errno($this->serveur))
2205 $err .= spip_sqlite_error($query, $this->serveur);
2206 return $t ? trace_query_end($query, $t, $r, $err, $this->serveur) : $r;
2207 }
2208
2209 function last_insert_id(){
2210 if ($this->sqlite_version==3)
2211 return $this->link->lastInsertId();
2212 else
2213 return sqlite_last_insert_rowid($this->link);
2214 }
2215 }
2216
2217
2218 /**
2219 * Cette classe est presente essentiellement pour un preg_replace_callback
2220 * avec des parametres dans la fonction appelee que l'on souhaite incrementer
2221 * (fonction pour proteger les textes)
2222 */
2223 class sqlite_traducteur {
2224 var $query = '';
2225 var $prefixe = ''; // le prefixe des tables
2226 var $sqlite_version = ''; // Version de sqlite (2 ou 3)
2227
2228 // Pour les corrections a effectuer sur les requetes :
2229 var $textes = array(); // array(code=>'texte') trouvé
2230
2231 function sqlite_traducteur($query, $prefixe, $sqlite_version){
2232 $this->query = $query;
2233 $this->prefixe = $prefixe;
2234 $this->sqlite_version = $sqlite_version;
2235 }
2236
2237 /**
2238 * transformer la requete pour sqlite
2239 * enleve les textes, transforme la requete pour quelle soit
2240 * bien interpretee par sqlite, puis remet les textes
2241 * la fonction affecte $this->query
2242 * http://doc.spip.org/@traduire_requete
2243 *
2244 * @return void
2245 */
2246 function traduire_requete(){
2247 //
2248 // 1) Protection des textes en les remplacant par des codes
2249 //
2250 // enlever les 'textes' et initialiser avec
2251 list($this->query, $textes) = query_echappe_textes($this->query);
2252
2253 //
2254 // 2) Corrections de la requete
2255 //
2256 // Correction Create Database
2257 // Create Database -> requete ignoree
2258 if (strpos($this->query, 'CREATE DATABASE')===0){
2259 spip_log("Sqlite : requete non executee -> $this->query", 'sqlite.'._LOG_AVERTISSEMENT);
2260 $this->query = "SELECT 1";
2261 }
2262
2263 // Correction Insert Ignore
2264 // INSERT IGNORE -> insert (tout court et pas 'insert or replace')
2265 if (strpos($this->query, 'INSERT IGNORE')===0){
2266 spip_log("Sqlite : requete transformee -> $this->query", 'sqlite.'._LOG_DEBUG);
2267 $this->query = 'INSERT '.substr($this->query, '13');
2268 }
2269
2270 // Correction des dates avec INTERVAL
2271 // utiliser sql_date_proche() de preference
2272 if (strpos($this->query, 'INTERVAL')!==false){
2273 $this->query = preg_replace_callback("/DATE_(ADD|SUB)(.*)INTERVAL\s+(\d+)\s+([a-zA-Z]+)\)/U",
2274 array(&$this, '_remplacerDateParTime'),
2275 $this->query);
2276 }
2277
2278 if (strpos($this->query, 'LEFT(')!==false){
2279 $this->query = str_replace('LEFT(','_LEFT(',$this->query);
2280 }
2281
2282 if (strpos($this->query, 'TIMESTAMPDIFF(')!==false){
2283 $this->query = preg_replace('/TIMESTAMPDIFF\(\s*([^,]*)\s*,/Uims',"TIMESTAMPDIFF('\\1',",$this->query);
2284 }
2285
2286
2287 // Correction Using
2288 // USING (non reconnu en sqlite2)
2289 // problematique car la jointure ne se fait pas du coup.
2290 if (($this->sqlite_version==2) && (strpos($this->query, "USING")!==false)){
2291 spip_log("'USING (champ)' n'est pas reconnu en SQLite 2. Utilisez 'ON table1.champ = table2.champ'", 'sqlite.'._LOG_ERREUR);
2292 $this->query = preg_replace('/USING\s*\([^\)]*\)/', '', $this->query);
2293 }
2294
2295 // Correction Field
2296 // remplace FIELD(table,i,j,k...) par CASE WHEN table=i THEN n ... ELSE 0 END
2297 if (strpos($this->query, 'FIELD')!==false){
2298 $this->query = preg_replace_callback('/FIELD\s*\(([^\)]*)\)/',
2299 array(&$this, '_remplacerFieldParCase'),
2300 $this->query);
2301 }
2302
2303 // Correction des noms de tables FROM
2304 // mettre les bons noms de table dans from, update, insert, replace...
2305 if (preg_match('/\s(SET|VALUES|WHERE|DATABASE)\s/iS', $this->query, $regs)){
2306 $suite = strstr($this->query, $regs[0]);
2307 $this->query = substr($this->query, 0, -strlen($suite));
2308 }
2309 else
2310 $suite = '';
2311 $pref = ($this->prefixe) ? $this->prefixe."_" : "";
2312 $this->query = preg_replace('/([,\s])spip_/S', '\1'.$pref, $this->query).$suite;
2313
2314 // Correction zero AS x
2315 // pg n'aime pas 0+x AS alias, sqlite, dans le meme style,
2316 // n'apprecie pas du tout SELECT 0 as x ... ORDER BY x
2317 // il dit que x ne doit pas ĂȘtre un integer dans le order by !
2318 // on remplace du coup x par vide() dans ce cas uniquement
2319 //
2320 // rien que pour public/vertebrer.php ?
2321 if ((strpos($this->query, "0 AS")!==false)){
2322 // on ne remplace que dans ORDER BY ou GROUP BY
2323 if (preg_match('/\s(ORDER|GROUP) BY\s/i', $this->query, $regs)){
2324 $suite = strstr($this->query, $regs[0]);
2325 $this->query = substr($this->query, 0, -strlen($suite));
2326
2327 // on cherche les noms des x dans 0 AS x
2328 // on remplace dans $suite le nom par vide()
2329 preg_match_all('/\b0 AS\s*([^\s,]+)/', $this->query, $matches, PREG_PATTERN_ORDER);
2330 foreach ($matches[1] as $m){
2331 $suite = str_replace($m, 'VIDE()', $suite);
2332 }
2333 $this->query .= $suite;
2334 }
2335 }
2336
2337 // Correction possible des divisions entieres
2338 // Le standard SQL (lequel? ou?) semble indiquer que
2339 // a/b=c doit donner c entier si a et b sont entiers 4/3=1.
2340 // C'est ce que retournent effectivement SQL Server et SQLite
2341 // Ce n'est pas ce qu'applique MySQL qui retourne un reel : 4/3=1.333...
2342 //
2343 // On peut forcer la conversion en multipliant par 1.0 avant la division
2344 // /!\ SQLite 3.5.9 Debian/Ubuntu est victime d'un bug en plus !
2345 // cf. https://bugs.launchpad.net/ubuntu/+source/sqlite3/+bug/254228
2346 // http://www.sqlite.org/cvstrac/tktview?tn=3202
2347 // (4*1.0/3) n'est pas rendu dans ce cas !
2348 # $this->query = str_replace('/','* 1.00 / ',$this->query);
2349
2350
2351 // Correction critere REGEXP, non reconnu en sqlite2
2352 if (($this->sqlite_version==2) && (strpos($this->query, 'REGEXP')!==false)){
2353 $this->query = preg_replace('/([^\s\(]*)(\s*)REGEXP(\s*)([^\s\)]*)/', 'REGEXP($4, $1)', $this->query);
2354 }
2355
2356 //
2357 // 3) Remise en place des textes d'origine
2358 //
2359 // Correction Antiquotes et echappements
2360 // ` => rien
2361 if (strpos($this->query,'`')!==false)
2362 $this->query = str_replace('`','', $this->query);
2363
2364 $this->query = query_reinjecte_textes($this->query, $textes);
2365
2366 return $this->query;
2367 }
2368
2369
2370 /**
2371 * les callbacks
2372 * remplacer DATE_ / INTERVAL par DATE...strtotime
2373 * http://doc.spip.org/@_remplacerDateParTime
2374 *
2375 * @param $matches
2376 * @return string
2377 */
2378 function _remplacerDateParTime($matches){
2379 $op = strtoupper($matches[1]=='ADD') ? '+' : '-';
2380 return "datetime$matches[2] '$op$matches[3] $matches[4]')";
2381 }
2382
2383 /**
2384 * callback ou l'on remplace FIELD(table,i,j,k...) par CASE WHEN table=i THEN n ... ELSE 0 END
2385 * http://doc.spip.org/@_remplacerFieldParCase
2386 *
2387 * @param $matches
2388 * @return string
2389 */
2390 function _remplacerFieldParCase($matches){
2391 $fields = substr($matches[0], 6, -1); // ne recuperer que l'interieur X de field(X)
2392 $t = explode(',', $fields);
2393 $index = array_shift($t);
2394
2395 $res = '';
2396 $n = 0;
2397 foreach ($t as $v){
2398 $n++;
2399 $res .= "\nWHEN $index=$v THEN $n";
2400 }
2401 return "CASE $res ELSE 0 END ";
2402 }
2403
2404 }
2405
2406 ?>