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