[SPIP] ~v3.2.3-->v3.2.4
[lhc/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-2019 *
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 gérant
15 * les instructions SQL pour Sqlite
16 *
17 * @package SPIP\Core\SQL\SQLite
18 */
19
20 if (!defined('_ECRIRE_INC_VERSION')) {
21 return;
22 }
23
24 // TODO: get/set_caracteres ?
25
26
27 /*
28 * regroupe le maximum de fonctions qui peuvent cohabiter
29 * D'abord les fonctions d'abstractions de SPIP
30 */
31
32 /**
33 * Connecteur à une base SQLite
34 *
35 * @param string $addr
36 * @param int $port
37 * @param string $login
38 * @param string $pass
39 * @param string $db
40 * @param string $prefixe
41 * @param string $sqlite_version
42 * @return array|bool
43 */
44 function req_sqlite_dist($addr, $port, $login, $pass, $db = '', $prefixe = '', $sqlite_version = '') {
45 static $last_connect = array();
46
47 // si provient de selectdb
48 // un code pour etre sur que l'on vient de select_db()
49 if (strpos($db, $code = '@selectdb@') !== false) {
50 foreach (array('addr', 'port', 'login', 'pass', 'prefixe') as $a) {
51 $$a = $last_connect[$a];
52 }
53 $db = str_replace($code, '', $db);
54 }
55
56 /*
57 * En sqlite, seule l'adresse du fichier est importante.
58 * Ce sera $db le nom,
59 * le path est $addr
60 * (_DIR_DB si $addr est vide)
61 */
62 _sqlite_init();
63
64 // determiner le dossier de la base : $addr ou _DIR_DB
65 $f = _DIR_DB;
66 if ($addr and strpos($addr, '/') !== false) {
67 $f = rtrim($addr, '/') . '/';
68 }
69
70 // un nom de base demande et impossible d'obtenir la base, on s'en va :
71 // il faut que la base existe ou que le repertoire parent soit writable
72 if ($db and !is_file($f .= $db . '.sqlite') and !is_writable(dirname($f))) {
73 spip_log("base $f non trouvee ou droits en ecriture manquants", 'sqlite.' . _LOG_HS);
74
75 return false;
76 }
77
78 // charger les modules sqlite au besoin
79 if (!_sqlite_charger_version($sqlite_version)) {
80 spip_log("Impossible de trouver/charger le module SQLite ($sqlite_version)!", 'sqlite.' . _LOG_HS);
81
82 return false;
83 }
84
85 // chargement des constantes
86 // il ne faut pas definir les constantes avant d'avoir charge les modules sqlite
87 $define = "spip_sqlite" . $sqlite_version . "_constantes";
88 $define();
89
90 $ok = false;
91 if (!$db) {
92 // si pas de db ->
93 // base temporaire tant qu'on ne connait pas son vrai nom
94 // pour tester la connexion
95 $db = "_sqlite" . $sqlite_version . "_install";
96 $tmp = _DIR_DB . $db . ".sqlite";
97 if ($sqlite_version == 3) {
98 $ok = $link = new PDO("sqlite:$tmp");
99 } else {
100 $ok = $link = sqlite_open($tmp, _SQLITE_CHMOD, $err);
101 }
102 } else {
103 // Ouvrir (eventuellement creer la base)
104 // si pas de version fourni, on essaie la 3, sinon la 2
105 if ($sqlite_version == 3) {
106 $ok = $link = new PDO("sqlite:$f");
107 } else {
108 $ok = $link = sqlite_open($f, _SQLITE_CHMOD, $err);
109 }
110 }
111
112 if (!$ok) {
113 $e = sqlite_last_error($db);
114 spip_log("Impossible d'ouvrir la base SQLite($sqlite_version) $f : $e", 'sqlite.' . _LOG_HS);
115
116 return false;
117 }
118
119 if ($link) {
120 $last_connect = array(
121 'addr' => $addr,
122 'port' => $port,
123 'login' => $login,
124 'pass' => $pass,
125 'db' => $db,
126 'prefixe' => $prefixe,
127 );
128 // etre sur qu'on definit bien les fonctions a chaque nouvelle connexion
129 include_spip('req/sqlite_fonctions');
130 _sqlite_init_functions($link);
131 }
132
133 return array(
134 'db' => $db,
135 'prefixe' => $prefixe ? $prefixe : $db,
136 'link' => $link,
137 'total_requetes' => 0,
138 );
139 }
140
141
142 /**
143 * Fonction de requete generale, munie d'une trace a la demande
144 *
145 * @param string $query
146 * Requete a executer
147 * @param string $serveur
148 * Nom du connecteur
149 * @param bool $requeter
150 * Effectuer la requete ?
151 * - true pour executer
152 * - false pour retourner le texte de la requete
153 * @return bool|SQLiteResult|string
154 * Resultat de la requete
155 */
156 function spip_sqlite_query($query, $serveur = '', $requeter = true) {
157 #spip_log("spip_sqlite_query() > $query",'sqlite.'._LOG_DEBUG);
158 #_sqlite_init(); // fait la premiere fois dans spip_sqlite
159 $query = spip_sqlite::traduire_requete($query, $serveur);
160 if (!$requeter) {
161 return $query;
162 }
163
164 return spip_sqlite::executer_requete($query, $serveur);
165 }
166
167
168 /* ordre alphabetique pour les autres */
169
170 /**
171 * Modifie une structure de table SQLite
172 *
173 * @param string $query Requête SQL (sans 'ALTER ')
174 * @param string $serveur Nom de la connexion
175 * @param bool $requeter inutilisé
176 * @return bool
177 * False si erreur dans l'exécution, true sinon
178 */
179 function spip_sqlite_alter($query, $serveur = '', $requeter = true) {
180
181 $query = spip_sqlite_query("ALTER $query", $serveur, false);
182 // traduire la requete pour recuperer les bons noms de table
183 $query = spip_sqlite::traduire_requete($query, $serveur);
184
185 /*
186 * la il faut faire les transformations
187 * si ALTER TABLE x (DROP|CHANGE) y
188 *
189 * 1) recuperer "ALTER TABLE table "
190 * 2) spliter les sous requetes (,)
191 * 3) faire chaque requete independemment
192 */
193
194 // 1
195 if (preg_match("/\s*(ALTER(\s*IGNORE)?\s*TABLE\s*([^\s]*))\s*(.*)?/is", $query, $regs)) {
196 $debut = $regs[1];
197 $table = $regs[3];
198 $suite = $regs[4];
199 } else {
200 spip_log("SQLite : Probleme de ALTER TABLE mal forme dans $query", 'sqlite.' . _LOG_ERREUR);
201
202 return false;
203 }
204
205 // 2
206 // il faudrait une regexp pour eviter de spliter ADD PRIMARY KEY (colA, colB)
207 // tout en cassant "ADD PRIMARY KEY (colA, colB), ADD INDEX (chose)"... en deux
208 // ou revoir l'api de sql_alter en creant un
209 // sql_alter_table($table,array($actions));
210 $todo = explode(',', $suite);
211
212 // on remet les morceaux dechires ensembles... que c'est laid !
213 $todo2 = array();
214 $i = 0;
215 $ouverte = false;
216 while ($do = array_shift($todo)) {
217 $todo2[$i] = isset($todo2[$i]) ? $todo2[$i] . "," . $do : $do;
218 $o = (false !== strpos($do, "("));
219 $f = (false !== strpos($do, ")"));
220 if ($o and !$f) {
221 $ouverte = true;
222 } elseif ($f) {
223 $ouverte = false;
224 }
225 if (!$ouverte) {
226 $i++;
227 }
228 }
229
230 // 3
231 $resultats = array();
232 foreach ($todo2 as $do) {
233 $do = trim($do);
234 if (!preg_match('/(DROP PRIMARY KEY|DROP KEY|DROP INDEX|DROP COLUMN|DROP'
235 . '|CHANGE COLUMN|CHANGE|MODIFY|RENAME TO|RENAME'
236 . '|ADD PRIMARY KEY|ADD KEY|ADD INDEX|ADD UNIQUE KEY|ADD UNIQUE'
237 . '|ADD COLUMN|ADD'
238 . ')\s*([^\s]*)\s*(.*)?/i', $do, $matches)
239 ) {
240 spip_log("SQLite : Probleme de ALTER TABLE, utilisation non reconnue dans : $do \n(requete d'origine : $query)",
241 'sqlite.' . _LOG_ERREUR);
242
243 return false;
244 }
245
246 $cle = strtoupper($matches[1]);
247 $colonne_origine = $matches[2];
248 $colonne_destination = '';
249
250 $def = $matches[3];
251
252 // eluder une eventuelle clause before|after|first inutilisable
253 $defr = rtrim(preg_replace('/(BEFORE|AFTER|FIRST)(.*)$/is', '', $def));
254 $defo = $defr; // garder la def d'origine pour certains cas
255 // remplacer les definitions venant de mysql
256 $defr = _sqlite_remplacements_definitions_table($defr);
257
258 // reinjecter dans le do
259 $do = str_replace($def, $defr, $do);
260 $def = $defr;
261
262 switch ($cle) {
263 // suppression d'un index
264 case 'DROP KEY':
265 case 'DROP INDEX':
266 $nom_index = $colonne_origine;
267 spip_sqlite_drop_index($nom_index, $table, $serveur);
268 break;
269
270 // suppression d'une pk
271 case 'DROP PRIMARY KEY':
272 if (!_sqlite_modifier_table(
273 $table,
274 $colonne_origine,
275 array('key' => array('PRIMARY KEY' => '')),
276 $serveur)
277 ) {
278 return false;
279 }
280 break;
281 // suppression d'une colonne
282 case 'DROP COLUMN':
283 case 'DROP':
284 if (!_sqlite_modifier_table(
285 $table,
286 array($colonne_origine => ""),
287 array(),
288 $serveur)
289 ) {
290 return false;
291 }
292 break;
293
294 case 'CHANGE COLUMN':
295 case 'CHANGE':
296 // recuperer le nom de la future colonne
297 // on reprend la def d'origine car _sqlite_modifier_table va refaire la translation
298 // en tenant compte de la cle primaire (ce qui est mieux)
299 $def = trim($defo);
300 $colonne_destination = substr($def, 0, strpos($def, ' '));
301 $def = substr($def, strlen($colonne_destination) + 1);
302
303 if (!_sqlite_modifier_table(
304 $table,
305 array($colonne_origine => $colonne_destination),
306 array('field' => array($colonne_destination => $def)),
307 $serveur)
308 ) {
309 return false;
310 }
311 break;
312
313 case 'MODIFY':
314 // on reprend la def d'origine car _sqlite_modifier_table va refaire la translation
315 // en tenant compte de la cle primaire (ce qui est mieux)
316 if (!_sqlite_modifier_table(
317 $table,
318 $colonne_origine,
319 array('field' => array($colonne_origine => $defo)),
320 $serveur)
321 ) {
322 return false;
323 }
324 break;
325
326 // pas geres en sqlite2
327 case 'RENAME':
328 $do = "RENAME TO" . substr($do, 6);
329 case 'RENAME TO':
330 if (_sqlite_is_version(3, '', $serveur)) {
331 if (!spip_sqlite::executer_requete("$debut $do", $serveur)) {
332 spip_log("SQLite : Erreur ALTER TABLE / RENAME : $query", 'sqlite.' . _LOG_ERREUR);
333
334 return false;
335 }
336 // artillerie lourde pour sqlite2 !
337 } else {
338 $table_dest = trim(substr($do, 9));
339 if (!_sqlite_modifier_table(array($table => $table_dest), '', array(), $serveur)) {
340 spip_log("SQLite : Erreur ALTER TABLE / RENAME : $query", 'sqlite.' . _LOG_ERREUR);
341
342 return false;
343 }
344 }
345 break;
346
347 // ajout d'une pk
348 case 'ADD PRIMARY KEY':
349 $pk = trim(substr($do, 16));
350 $pk = ($pk[0] == '(') ? substr($pk, 1, -1) : $pk;
351 if (!_sqlite_modifier_table(
352 $table,
353 $colonne_origine,
354 array('key' => array('PRIMARY KEY' => $pk)),
355 $serveur)
356 ) {
357 return false;
358 }
359 break;
360 // ajout d'un index
361 case 'ADD UNIQUE KEY':
362 case 'ADD UNIQUE':
363 $unique = true;
364 case 'ADD INDEX':
365 case 'ADD KEY':
366 if (!isset($unique)) {
367 $unique = false;
368 }
369 // peut etre "(colonne)" ou "nom_index (colonnes)"
370 // bug potentiel si qqn met "(colonne, colonne)"
371 //
372 // nom_index (colonnes)
373 if ($def) {
374 $colonnes = substr($def, 1, -1);
375 $nom_index = $colonne_origine;
376 } else {
377 // (colonne)
378 if ($colonne_origine[0] == "(") {
379 $colonnes = substr($colonne_origine, 1, -1);
380 if (false !== strpos(",", $colonnes)) {
381 spip_log(_LOG_GRAVITE_ERREUR, "SQLite : Erreur, impossible de creer un index sur plusieurs colonnes"
382 . " sans qu'il ait de nom ($table, ($colonnes))", 'sqlite');
383 break;
384 } else {
385 $nom_index = $colonnes;
386 }
387 } // nom_index
388 else {
389 $nom_index = $colonnes = $colonne_origine;
390 }
391 }
392 spip_sqlite_create_index($nom_index, $table, $colonnes, $unique, $serveur);
393 break;
394
395 // pas geres en sqlite2
396 case 'ADD COLUMN':
397 $do = "ADD" . substr($do, 10);
398 case 'ADD':
399 default:
400 if (_sqlite_is_version(3, '', $serveur) and !preg_match(',primary\s+key,i', $do)) {
401 if (!spip_sqlite::executer_requete("$debut $do", $serveur)) {
402 spip_log("SQLite : Erreur ALTER TABLE / ADD : $query", 'sqlite.' . _LOG_ERREUR);
403
404 return false;
405 }
406 break;
407
408 }
409 // artillerie lourde pour sqlite2 !
410 // ou si la colonne est aussi primary key
411 // cas du add id_truc int primary key
412 // ajout d'une colonne qui passe en primary key directe
413 else {
414 $def = trim(substr($do, 3));
415 $colonne_ajoutee = substr($def, 0, strpos($def, ' '));
416 $def = substr($def, strlen($colonne_ajoutee) + 1);
417 $opts = array();
418 if (preg_match(',primary\s+key,i', $def)) {
419 $opts['key'] = array('PRIMARY KEY' => $colonne_ajoutee);
420 $def = preg_replace(',primary\s+key,i', '', $def);
421 }
422 $opts['field'] = array($colonne_ajoutee => $def);
423 if (!_sqlite_modifier_table($table, array($colonne_ajoutee), $opts, $serveur)) {
424 spip_log("SQLite : Erreur ALTER TABLE / ADD : $query", 'sqlite.' . _LOG_ERREUR);
425
426 return false;
427 }
428 }
429 break;
430 }
431 // tout est bon, ouf !
432 spip_log("SQLite ($serveur) : Changements OK : $debut $do", 'sqlite.' . _LOG_INFO);
433 }
434
435 spip_log("SQLite ($serveur) : fin ALTER TABLE OK !", 'sqlite.' . _LOG_INFO);
436
437 return true;
438 }
439
440
441 /**
442 * Crée une table SQL
443 *
444 * Crée une table SQL nommee `$nom` à partir des 2 tableaux `$champs` et `$cles`
445 *
446 * @note Le nom des caches doit être inferieur à 64 caractères
447 *
448 * @param string $nom Nom de la table SQL
449 * @param array $champs Couples (champ => description SQL)
450 * @param array $cles Couples (type de clé => champ(s) de la clé)
451 * @param bool $autoinc True pour ajouter un auto-incrément sur la Primary Key
452 * @param bool $temporary True pour créer une table temporaire
453 * @param string $serveur Nom de la connexion
454 * @param bool $requeter Exécuter la requête, sinon la retourner
455 * @return array|null|resource|string
456 * - string Texte de la requête si demandée
457 * - true si la requête réussie, false sinon.
458 */
459 function spip_sqlite_create(
460 $nom,
461 $champs,
462 $cles,
463 $autoinc = false,
464 $temporary = false,
465 $serveur = '',
466 $requeter = true
467 ) {
468 $query = _sqlite_requete_create($nom, $champs, $cles, $autoinc, $temporary, $ifnotexists = true, $serveur, $requeter);
469 if (!$query) {
470 return false;
471 }
472 $res = spip_sqlite_query($query, $serveur, $requeter);
473
474 // SQLite ne cree pas les KEY sur les requetes CREATE TABLE
475 // il faut donc les faire creer ensuite
476 if (!$requeter) {
477 return $res;
478 }
479
480 $ok = $res ? true : false;
481 if ($ok) {
482 foreach ($cles as $k => $v) {
483 if (preg_match(',^(KEY|UNIQUE)\s,i', $k, $m)) {
484 $index = trim(substr($k, strlen($m[1])));
485 $unique = (strlen($m[1]) > 3);
486 $ok &= spip_sqlite_create_index($index, $nom, $v, $unique, $serveur);
487 }
488 }
489 }
490
491 return $ok ? true : false;
492 }
493
494 /**
495 * Crée une base de données SQLite
496 *
497 * @param string $nom Nom de la base (sans l'extension de fichier)
498 * @param string $serveur Nom de la connexion
499 * @param string $option Options
500 *
501 * @return bool true si la base est créee.
502 **/
503 function spip_sqlite_create_base($nom, $serveur = '', $option = true) {
504 $f = $nom . '.sqlite';
505 if (strpos($nom, "/") === false) {
506 $f = _DIR_DB . $f;
507 }
508 if (_sqlite_is_version(2, '', $serveur)) {
509 $ok = sqlite_open($f, _SQLITE_CHMOD, $err);
510 } else {
511 $ok = new PDO("sqlite:$f");
512 }
513 if ($ok) {
514 unset($ok);
515
516 return true;
517 }
518 unset($ok);
519
520 return false;
521 }
522
523
524 /**
525 * Crée une vue SQL nommée `$nom`
526 *
527 * @param string $nom
528 * Nom de la vue a creer
529 * @param string $query_select
530 * Texte de la requête de sélection servant de base à la vue
531 * @param string $serveur
532 * Nom du connecteur
533 * @param bool $requeter
534 * Effectuer la requete, sinon la retourner
535 * @return bool|SQLiteResult|string
536 * - true si la vue est créée
537 * - false si erreur ou si la vue existe déja
538 * - string texte de la requête si $requeter vaut false
539 */
540 function spip_sqlite_create_view($nom, $query_select, $serveur = '', $requeter = true) {
541 if (!$query_select) {
542 return false;
543 }
544 // vue deja presente
545 if (sql_showtable($nom, false, $serveur)) {
546 spip_log("Echec creation d'une vue sql ($nom) car celle-ci existe deja (serveur:$serveur)",
547 'sqlite.' . _LOG_ERREUR);
548
549 return false;
550 }
551
552 $query = "CREATE VIEW $nom AS " . $query_select;
553
554 return spip_sqlite_query($query, $serveur, $requeter);
555 }
556
557 /**
558 * Fonction de création d'un INDEX
559 *
560 * @param string $nom
561 * Nom de l'index
562 * @param string $table
563 * Table SQL de l'index
564 * @param string|array $champs
565 * Liste de champs sur lesquels s'applique l'index
566 * @param string|bool $unique
567 * Créer un index UNIQUE ?
568 * @param string $serveur
569 * Nom de la connexion sql utilisee
570 * @param bool $requeter
571 * true pour executer la requête ou false pour retourner le texte de la requête
572 * @return bool|string
573 * string : requête, false si erreur, true sinon.
574 */
575 function spip_sqlite_create_index($nom, $table, $champs, $unique = '', $serveur = '', $requeter = true) {
576 if (!($nom or $table or $champs)) {
577 spip_log("Champ manquant pour creer un index sqlite ($nom, $table, (" . join(',', $champs) . "))",
578 'sqlite.' . _LOG_ERREUR);
579
580 return false;
581 }
582
583 // SQLite ne differentie pas noms des index en fonction des tables
584 // il faut donc creer des noms uniques d'index pour une base sqlite
585 $nom = $table . '_' . $nom;
586 // enlever d'eventuelles parentheses deja presentes sur champs
587 if (!is_array($champs)) {
588 if ($champs[0] == "(") {
589 $champs = substr($champs, 1, -1);
590 }
591 $champs = array($champs);
592 // supprimer l'info de longueur d'index mysql en fin de champ
593 $champs = preg_replace(",\(\d+\)$,", "", $champs);
594 }
595
596 $ifnotexists = "";
597 $version = spip_sqlite_fetch(spip_sqlite_query("select sqlite_version() AS sqlite_version", $serveur), '', $serveur);
598 if (!function_exists('spip_version_compare')) {
599 include_spip('plugins/installer');
600 }
601
602 if ($version and spip_version_compare($version['sqlite_version'], '3.3.0', '>=')) {
603 $ifnotexists = ' IF NOT EXISTS';
604 } else {
605 /* simuler le IF EXISTS - version 2 et sqlite < 3.3a */
606 $a = spip_sqlite_showtable($table, $serveur);
607 if (isset($a['key']['KEY ' . $nom])) {
608 return true;
609 }
610 }
611
612 $query = "CREATE " . ($unique ? "UNIQUE " : "") . "INDEX$ifnotexists $nom ON $table (" . join(',', $champs) . ")";
613 $res = spip_sqlite_query($query, $serveur, $requeter);
614 if (!$requeter) {
615 return $res;
616 }
617 if ($res) {
618 return true;
619 } else {
620 return false;
621 }
622 }
623
624 /**
625 * Retourne le nombre de lignes d’une ressource de sélection obtenue
626 * avec `sql_select()`
627 *
628 * En PDO/sqlite3, il faut calculer le count par une requete count(*)
629 * pour les resultats de SELECT
630 * cela est fait sans spip_sqlite_query()
631 *
632 * @param Ressource|Object $r Ressource de résultat
633 * @param string $serveur Nom de la connexion
634 * @param bool $requeter Inutilisé
635 * @return int Nombre de lignes
636 */
637 function spip_sqlite_count($r, $serveur = '', $requeter = true) {
638 if (!$r) {
639 return 0;
640 }
641
642 if (_sqlite_is_version(3, '', $serveur)) {
643 // select ou autre (insert, update,...) ?
644
645 // (link,requete) a compter
646 if (is_array($r->spipSqliteRowCount)) {
647 list($link, $query) = $r->spipSqliteRowCount;
648 // amelioration possible a tester intensivement : pas de order by pour compter !
649 // $query = preg_replace(",ORDER BY .+(LIMIT\s|HAVING\s|GROUP BY\s|$),Uims","\\1",$query);
650 $query = "SELECT count(*) as zzzzsqlitecount FROM ($query)";
651 $l = $link->query($query);
652 $i = 0;
653 if ($l and $z = $l->fetch()) {
654 $i = $z['zzzzsqlitecount'];
655 }
656 $r->spipSqliteRowCount = $i;
657 }
658 if (isset($r->spipSqliteRowCount)) {
659 // Ce compte est faux s'il y a des limit dans la requete :(
660 // il retourne le nombre d'enregistrements sans le limit
661 return $r->spipSqliteRowCount;
662 } else {
663 return $r->rowCount();
664 }
665 } else {
666 return sqlite_num_rows($r);
667 }
668 }
669
670
671 /**
672 * Retourne le nombre de lignes d'une sélection
673 *
674 * @param array|string $from Tables à consulter (From)
675 * @param array|string $where Conditions a remplir (Where)
676 * @param array|string $groupby Critère de regroupement (Group by)
677 * @param array $having Tableau des des post-conditions à remplir (Having)
678 * @param string $serveur Nom de la connexion
679 * @param bool $requeter Exécuter la requête, sinon la retourner
680 * @return int|bool|string
681 * - String Texte de la requête si demandé
682 * - int Nombre de lignes
683 * - false si la requête a échouée
684 **/
685 function spip_sqlite_countsel(
686 $from = array(),
687 $where = array(),
688 $groupby = '',
689 $having = array(),
690 $serveur = '',
691 $requeter = true
692 ) {
693 $c = !$groupby ? '*' : ('DISTINCT ' . (is_string($groupby) ? $groupby : join(',', $groupby)));
694 $r = spip_sqlite_select("COUNT($c)", $from, $where, '', '', '',
695 $having, $serveur, $requeter);
696 if ((is_resource($r) or is_object($r)) && $requeter) { // ressource : sqlite2, object : sqlite3
697 if (_sqlite_is_version(3, '', $serveur)) {
698 list($r) = spip_sqlite_fetch($r, SPIP_SQLITE3_NUM, $serveur);
699 } else {
700 list($r) = spip_sqlite_fetch($r, SPIP_SQLITE2_NUM, $serveur);
701 }
702
703 }
704
705 return $r;
706 }
707
708
709 /**
710 * Supprime des enregistrements d'une table
711 *
712 * @param string $table Nom de la table SQL
713 * @param string|array $where Conditions à vérifier
714 * @param string $serveur Nom du connecteur
715 * @param bool $requeter Exécuter la requête, sinon la retourner
716 * @return bool|string
717 * - int : nombre de suppressions réalisées,
718 * - Texte de la requête si demandé,
719 * - False en cas d'erreur.
720 **/
721 function spip_sqlite_delete($table, $where = '', $serveur = '', $requeter = true) {
722 $res = spip_sqlite_query(
723 _sqlite_calculer_expression('DELETE FROM', $table, ',')
724 . _sqlite_calculer_expression('WHERE', $where),
725 $serveur, $requeter);
726
727 // renvoyer la requete inerte si demandee
728 if (!$requeter) {
729 return $res;
730 }
731
732 if ($res) {
733 $link = _sqlite_link($serveur);
734 if (_sqlite_is_version(3, $link)) {
735 return $res->rowCount();
736 } else {
737 return sqlite_changes($link);
738 }
739 } else {
740 return false;
741 }
742 }
743
744
745 /**
746 * Supprime une table SQL
747 *
748 * @param string $table Nom de la table SQL
749 * @param string $exist True pour ajouter un test d'existence avant de supprimer
750 * @param string $serveur Nom de la connexion
751 * @param bool $requeter Exécuter la requête, sinon la retourner
752 * @return bool|string
753 * - string Texte de la requête si demandé
754 * - true si la requête a réussie, false sinon
755 */
756 function spip_sqlite_drop_table($table, $exist = '', $serveur = '', $requeter = true) {
757 if ($exist) {
758 $exist = " IF EXISTS";
759 }
760
761 /* simuler le IF EXISTS - version 2 */
762 if ($exist && _sqlite_is_version(2, '', $serveur)) {
763 $a = spip_sqlite_showtable($table, $serveur);
764 if (!$a) {
765 return true;
766 }
767 $exist = '';
768 }
769 if (spip_sqlite_query("DROP TABLE$exist $table", $serveur, $requeter)) {
770 return true;
771 } else {
772 return false;
773 }
774 }
775
776
777 /**
778 * Supprime une vue SQL
779 *
780 * @param string $view Nom de la vue SQL
781 * @param string $exist True pour ajouter un test d'existence avant de supprimer
782 * @param string $serveur Nom de la connexion
783 * @param bool $requeter Exécuter la requête, sinon la retourner
784 * @return bool|string
785 * - string Texte de la requête si demandé
786 * - true si la requête a réussie, false sinon
787 */
788 function spip_sqlite_drop_view($view, $exist = '', $serveur = '', $requeter = true) {
789 if ($exist) {
790 $exist = " IF EXISTS";
791 }
792
793 /* simuler le IF EXISTS - version 2 */
794 if ($exist && _sqlite_is_version(2, '', $serveur)) {
795 $a = spip_sqlite_showtable($view, $serveur);
796 if (!$a) {
797 return true;
798 }
799 $exist = '';
800 }
801
802 return spip_sqlite_query("DROP VIEW$exist $view", $serveur, $requeter);
803 }
804
805 /**
806 * Fonction de suppression d'un INDEX
807 *
808 * @param string $nom : nom de l'index
809 * @param string $table : table sql de l'index
810 * @param string $serveur : nom de la connexion sql utilisee
811 * @param bool $requeter : true pour executer la requête ou false pour retourner le texte de la requête
812 *
813 * @return bool ou requete
814 */
815 function spip_sqlite_drop_index($nom, $table, $serveur = '', $requeter = true) {
816 if (!($nom or $table)) {
817 spip_log("Champ manquant pour supprimer un index sqlite ($nom, $table)", 'sqlite.' . _LOG_ERREUR);
818
819 return false;
820 }
821
822 // SQLite ne differentie pas noms des index en fonction des tables
823 // il faut donc creer des noms uniques d'index pour une base sqlite
824 $index = $table . '_' . $nom;
825 $exist = " IF EXISTS";
826
827 /* simuler le IF EXISTS - version 2 */
828 if (_sqlite_is_version(2, '', $serveur)) {
829 $a = spip_sqlite_showtable($table, $serveur);
830 if (!isset($a['key']['KEY ' . $nom])) {
831 return true;
832 }
833 $exist = '';
834 }
835
836 $query = "DROP INDEX$exist $index";
837
838 return spip_sqlite_query($query, $serveur, $requeter);
839 }
840
841 /**
842 * Retourne la dernière erreur generée
843 *
844 * @uses sql_error_backtrace()
845 *
846 * @param string $query
847 * Requête qui était exécutée
848 * @param string $serveur
849 * Nom de la connexion
850 * @return string
851 * Erreur eventuelle
852 **/
853 function spip_sqlite_error($query = '', $serveur = '') {
854 $link = _sqlite_link($serveur);
855
856 if (_sqlite_is_version(3, $link)) {
857 $errs = $link->errorInfo();
858 /*
859 $errs[0]
860 numero SQLState ('HY000' souvent lors d'une erreur)
861 http://www.easysoft.com/developer/interfaces/odbc/sqlstate_status_return_codes.html
862 $errs[1]
863 numéro d'erreur SQLite (souvent 1 lors d'une erreur)
864 http://www.sqlite.org/c3ref/c_abort.html
865 $errs[2]
866 Le texte du message d'erreur
867 */
868 $s = '';
869 if (ltrim($errs[0], '0')) { // 00000 si pas d'erreur
870 $s = "$errs[2]";
871 }
872 } elseif ($link) {
873 $s = sqlite_error_string(sqlite_last_error($link));
874 } else {
875 $s = ": aucune ressource sqlite (link)";
876 }
877 if ($s) {
878 $trace = debug_backtrace();
879 if ($trace[0]['function'] != "spip_mysql_error") {
880 spip_log("$s - $query - " . sql_error_backtrace(), 'sqlite.' . _LOG_ERREUR);
881 }
882 }
883
884 return $s;
885 }
886
887 /**
888 * Retourne le numero de la dernière erreur SQL
889 *
890 * Le numéro (en sqlite3/pdo) est un retour ODBC tel que (très souvent) HY000
891 * http://www.easysoft.com/developer/interfaces/odbc/sqlstate_status_return_codes.html
892 *
893 * @param string $serveur
894 * nom de la connexion
895 * @return int|string
896 * 0 pas d'erreur
897 * 1 ou autre erreur (en sqlite 2)
898 * 'HY000/1' : numéro de l'erreur SQLState / numéro d'erreur interne SQLite (en sqlite 3)
899 **/
900 function spip_sqlite_errno($serveur = '') {
901 $link = _sqlite_link($serveur);
902
903 if (_sqlite_is_version(3, $link)) {
904 $t = $link->errorInfo();
905 $s = ltrim($t[0], '0'); // 00000 si pas d'erreur
906 if ($s) {
907 $s .= ' / ' . $t[1];
908 } // ajoute l'erreur du moteur SQLite
909 } elseif ($link) {
910 $s = sqlite_last_error($link);
911 } else {
912 $s = ": aucune ressource sqlite (link)";
913 }
914
915 if ($s) {
916 spip_log("Erreur sqlite $s", 'sqlite.' . _LOG_ERREUR);
917 }
918
919 return $s ? $s : 0;
920 }
921
922
923 /**
924 * Retourne une explication de requête (Explain) SQLite
925 *
926 * @param string $query Texte de la requête
927 * @param string $serveur Nom de la connexion
928 * @param bool $requeter Exécuter la requête, sinon la retourner
929 * @return array|string|bool
930 * - array : Tableau de l'explication
931 * - string si on retourne le texte de la requête
932 * - false si on a pas pu avoir d'explication
933 */
934 function spip_sqlite_explain($query, $serveur = '', $requeter = true) {
935 if (strpos(ltrim($query), 'SELECT') !== 0) {
936 return array();
937 }
938
939 $query = spip_sqlite::traduire_requete($query, $serveur);
940 $query = 'EXPLAIN ' . $query;
941 if (!$requeter) {
942 return $query;
943 }
944 // on ne trace pas ces requetes, sinon on obtient un tracage sans fin...
945 $r = spip_sqlite::executer_requete($query, $serveur, false);
946
947 return $r ? spip_sqlite_fetch($r, null, $serveur) : false; // hum ? etrange ca... a verifier
948 }
949
950
951 /**
952 * Rècupère une ligne de résultat
953 *
954 * Récupère la ligne suivante d'une ressource de résultat
955 *
956 * @param Ressource $r Ressource de résultat (issu de sql_select)
957 * @param string $t Structure de résultat attendu (défaut ASSOC)
958 * @param string $serveur Nom de la connexion
959 * @param bool $requeter Inutilisé
960 * @return array Ligne de résultat
961 */
962 function spip_sqlite_fetch($r, $t = '', $serveur = '', $requeter = true) {
963
964 $link = _sqlite_link($serveur);
965 $is_v3 = _sqlite_is_version(3, $link);
966 if (!$t) {
967 $t = ($is_v3 ? SPIP_SQLITE3_ASSOC : SPIP_SQLITE2_ASSOC);
968 }
969
970 $retour = false;
971 if ($r) {
972 $retour = ($is_v3 ? $r->fetch($t) : sqlite_fetch_array($r, $t));
973 }
974
975 // les version 2 et 3 parfois renvoie des 'table.titre' au lieu de 'titre' tout court ! pff !
976 // suppression de 'table.' pour toutes les cles (c'est un peu violent !)
977 // c'est couteux : on ne verifie que la premiere ligne pour voir si on le fait ou non
978 if ($retour
979 and strpos(implode('', array_keys($retour)), '.') !== false
980 ) {
981 foreach ($retour as $cle => $val) {
982 if (($pos = strpos($cle, '.')) !== false) {
983 $retour[substr($cle, $pos + 1)] = &$retour[$cle];
984 unset($retour[$cle]);
985 }
986 }
987 }
988
989 return $retour;
990 }
991
992 /**
993 * Place le pointeur de résultat sur la position indiquée
994 *
995 * @param Ressource $r Ressource de résultat
996 * @param int $row_number Position. Déplacer le pointeur à cette ligne
997 * @param string $serveur Nom de la connexion
998 * @param bool $requeter Inutilisé
999 * @return bool True si déplacement réussi, false sinon.
1000 **/
1001 function spip_sqlite_seek($r, $row_number, $serveur = '', $requeter = true) {
1002 if ($r) {
1003 $link = _sqlite_link($serveur);
1004 if (_sqlite_is_version(3, $link)) {
1005 // encore un truc de bien fichu : PDO ne PEUT PAS faire de seek ou de rewind...
1006 // je me demande si pour sqlite 3 il ne faudrait pas mieux utiliser
1007 // les nouvelles fonctions sqlite3_xx (mais encore moins presentes...)
1008 return false;
1009 } else {
1010 return sqlite_seek($r, $row_number);
1011 }
1012 }
1013 }
1014
1015
1016 /**
1017 * Libère une ressource de résultat
1018 *
1019 * Indique à SQLite de libérer de sa mémoire la ressoucre de résultat indiquée
1020 * car on n'a plus besoin de l'utiliser.
1021 *
1022 * @param Ressource|Object $r Ressource de résultat
1023 * @param string $serveur Nom de la connexion
1024 * @param bool $requeter Inutilisé
1025 * @return bool True si réussi
1026 */
1027 function spip_sqlite_free(&$r, $serveur = '', $requeter = true) {
1028 unset($r);
1029
1030 return true;
1031 //return sqlite_free_result($r);
1032 }
1033
1034
1035 /**
1036 * Teste si le charset indiqué est disponible sur le serveur SQL (aucune action ici)
1037 *
1038 * Cette fonction n'a aucune action actuellement
1039 *
1040 * @param array|string $charset Nom du charset à tester.
1041 * @param string $serveur Nom de la connexion
1042 * @param bool $requeter inutilisé
1043 * @return void
1044 */
1045 function spip_sqlite_get_charset($charset = array(), $serveur = '', $requeter = true) {
1046 //$c = !$charset ? '' : (" LIKE "._q($charset['charset']));
1047 //return spip_sqlite_fetch(sqlite_query(_sqlite_link($serveur), "SHOW CHARACTER SET$c"), NULL, $serveur);
1048 }
1049
1050
1051 /**
1052 * Prépare une chaîne hexadécimale
1053 *
1054 * Par exemple : FF ==> 255 en SQLite
1055 *
1056 * @param string $v
1057 * Chaine hexadecimale
1058 * @return string
1059 * Valeur hexadécimale pour SQLite
1060 **/
1061 function spip_sqlite_hex($v) {
1062 return hexdec($v);
1063 }
1064
1065
1066 /**
1067 * Retourne une expression IN pour le gestionnaire de base de données
1068 *
1069 * IN (...) est limité à 255 éléments, d'où cette fonction assistante
1070 *
1071 * @param string $val
1072 * Colonne SQL sur laquelle appliquer le test
1073 * @param string|array $valeurs
1074 * Liste des valeurs possibles (séparés par des virgules si string)
1075 * @param string $not
1076 * - '' sélectionne les éléments correspondant aux valeurs
1077 * - 'NOT' inverse en sélectionnant les éléments ne correspondant pas aux valeurs
1078 * @param string $serveur
1079 * Nom du connecteur
1080 * @param bool $requeter
1081 * Inutilisé
1082 * @return string
1083 * Expression de requête SQL
1084 **/
1085 function spip_sqlite_in($val, $valeurs, $not = '', $serveur = '', $requeter = true) {
1086 $n = $i = 0;
1087 $in_sql = "";
1088 while ($n = strpos($valeurs, ',', $n + 1)) {
1089 if ((++$i) >= 255) {
1090 $in_sql .= "($val $not IN (" .
1091 substr($valeurs, 0, $n) .
1092 "))\n" .
1093 ($not ? "AND\t" : "OR\t");
1094 $valeurs = substr($valeurs, $n + 1);
1095 $i = $n = 0;
1096 }
1097 }
1098 $in_sql .= "($val $not IN ($valeurs))";
1099
1100 return "($in_sql)";
1101 }
1102
1103
1104 /**
1105 * Insère une ligne dans une table
1106 *
1107 * @param string $table
1108 * Nom de la table SQL
1109 * @param string $champs
1110 * Liste des colonnes impactées,
1111 * @param string $valeurs
1112 * Liste des valeurs,
1113 * @param array $desc
1114 * Tableau de description des colonnes de la table SQL utilisée
1115 * (il sera calculé si nécessaire s'il n'est pas transmis).
1116 * @param string $serveur
1117 * Nom du connecteur
1118 * @param bool $requeter
1119 * Exécuter la requête, sinon la retourner
1120 * @return bool|string|int|array
1121 * - int|true identifiant de l'élément inséré (si possible), ou true, si réussite
1122 * - Texte de la requête si demandé,
1123 * - False en cas d'erreur,
1124 * - Tableau de description de la requête et du temps d'exécution, si var_profile activé
1125 **/
1126 function spip_sqlite_insert($table, $champs, $valeurs, $desc = array(), $serveur = '', $requeter = true) {
1127
1128 $query = "INSERT INTO $table " . ($champs ? "$champs VALUES $valeurs" : "DEFAULT VALUES");
1129 if ($r = spip_sqlite_query($query, $serveur, $requeter)) {
1130 if (!$requeter) {
1131 return $r;
1132 }
1133 $nb = spip_sqlite::last_insert_id($serveur);
1134 } else {
1135 $nb = false;
1136 }
1137
1138 $err = spip_sqlite_error($query, $serveur);
1139
1140 // cas particulier : ne pas substituer la reponse spip_sqlite_query si on est en profilage
1141 return isset($_GET['var_profile']) ? $r : $nb;
1142
1143 }
1144
1145
1146 /**
1147 * Insère une ligne dans une table, en protégeant chaque valeur
1148 *
1149 * @param string $table
1150 * Nom de la table SQL
1151 * @param string $couples
1152 * Couples (colonne => valeur)
1153 * @param array $desc
1154 * Tableau de description des colonnes de la table SQL utilisée
1155 * (il sera calculé si nécessaire s'il n'est pas transmis).
1156 * @param string $serveur
1157 * Nom du connecteur
1158 * @param bool $requeter
1159 * Exécuter la requête, sinon la retourner
1160 * @return bool|string|int|array
1161 * - int|true identifiant de l'élément inséré (si possible), ou true, si réussite
1162 * - Texte de la requête si demandé,
1163 * - False en cas d'erreur,
1164 * - Tableau de description de la requête et du temps d'exécution, si var_profile activé
1165 **/
1166 function spip_sqlite_insertq($table, $couples = array(), $desc = array(), $serveur = '', $requeter = true) {
1167 if (!$desc) {
1168 $desc = description_table($table, $serveur);
1169 }
1170 if (!$desc) {
1171 die("$table insertion sans description");
1172 }
1173 $fields = isset($desc['field']) ? $desc['field'] : array();
1174
1175 foreach ($couples as $champ => $val) {
1176 $couples[$champ] = _sqlite_calculer_cite($val, $fields[$champ]);
1177 }
1178
1179 // recherche de champs 'timestamp' pour mise a jour auto de ceux-ci
1180 $couples = _sqlite_ajouter_champs_timestamp($table, $couples, $desc, $serveur);
1181
1182 $cles = $valeurs = "";
1183 if (count($couples)) {
1184 $cles = "(" . join(',', array_keys($couples)) . ")";
1185 $valeurs = "(" . join(',', $couples) . ")";
1186 }
1187
1188 return spip_sqlite_insert($table, $cles, $valeurs, $desc, $serveur, $requeter);
1189 }
1190
1191
1192 /**
1193 * Insère plusieurs lignes d'un coup dans une table
1194 *
1195 * @param string $table
1196 * Nom de la table SQL
1197 * @param array $tab_couples
1198 * Tableau de tableaux associatifs (colonne => valeur)
1199 * @param array $desc
1200 * Tableau de description des colonnes de la table SQL utilisée
1201 * (il sera calculé si nécessaire s'il n'est pas transmis).
1202 * @param string $serveur
1203 * Nom du connecteur
1204 * @param bool $requeter
1205 * Exécuter la requête, sinon la retourner
1206 * @return bool|string
1207 * - True en cas de succès,
1208 * - Texte de la requête si demandé,
1209 * - False en cas d'erreur.
1210 **/
1211 function spip_sqlite_insertq_multi($table, $tab_couples = array(), $desc = array(), $serveur = '', $requeter = true) {
1212 if (!$desc) {
1213 $desc = description_table($table, $serveur);
1214 }
1215 if (!$desc) {
1216 die("$table insertion sans description");
1217 }
1218 if (!isset($desc['field'])) {
1219 $desc['field'] = array();
1220 }
1221
1222 // recuperer les champs 'timestamp' pour mise a jour auto de ceux-ci
1223 $maj = _sqlite_ajouter_champs_timestamp($table, array(), $desc, $serveur);
1224
1225 // seul le nom de la table est a traduire ici :
1226 // le faire une seule fois au debut
1227 $query_start = "INSERT INTO $table ";
1228 $query_start = spip_sqlite::traduire_requete($query_start, $serveur);
1229
1230 // ouvrir une transaction
1231 if ($requeter) {
1232 spip_sqlite::demarrer_transaction($serveur);
1233 }
1234
1235 while ($couples = array_shift($tab_couples)) {
1236 foreach ($couples as $champ => $val) {
1237 $couples[$champ] = _sqlite_calculer_cite($val, $desc['field'][$champ]);
1238 }
1239
1240 // inserer les champs timestamp par defaut
1241 $couples = array_merge($maj, $couples);
1242
1243 $champs = $valeurs = "";
1244 if (count($couples)) {
1245 $champs = "(" . join(',', array_keys($couples)) . ")";
1246 $valeurs = "(" . join(',', $couples) . ")";
1247 $query = $query_start . "$champs VALUES $valeurs";
1248 } else {
1249 $query = $query_start . "DEFAULT VALUES";
1250 }
1251
1252 if ($requeter) {
1253 $retour = spip_sqlite::executer_requete($query, $serveur);
1254 }
1255
1256 // sur le dernier couple uniquement
1257 if (!count($tab_couples)) {
1258 $nb = 0;
1259 if ($requeter) {
1260 $nb = spip_sqlite::last_insert_id($serveur);
1261 } else {
1262 return $query;
1263 }
1264 }
1265
1266 $err = spip_sqlite_error($query, $serveur);
1267 }
1268
1269 if ($requeter) {
1270 spip_sqlite::finir_transaction($serveur);
1271 }
1272
1273 // renvoie le dernier id d'autoincrement ajoute
1274 // cas particulier : ne pas substituer la reponse spip_sqlite_query si on est en profilage
1275 return isset($_GET['var_profile']) ? $retour : $nb;
1276 }
1277
1278
1279 /**
1280 * Retourne si le moteur SQL préfère utiliser des transactions.
1281 *
1282 * @param string $serveur
1283 * Nom du connecteur
1284 * @param bool $requeter
1285 * Inutilisé
1286 * @return bool
1287 * Toujours true.
1288 **/
1289 function spip_sqlite_preferer_transaction($serveur = '', $requeter = true) {
1290 return true;
1291 }
1292
1293 /**
1294 * Démarre une transaction
1295 *
1296 * Pratique pour des sql_updateq() dans un foreach,
1297 * parfois 100* plus rapide s'ils sont nombreux en sqlite !
1298 *
1299 * @param string $serveur
1300 * Nom du connecteur
1301 * @param bool $requeter
1302 * true pour exécuter la requête ou false pour retourner le texte de la requête
1303 * @return bool|string
1304 * string si texte de la requête demandé, true sinon
1305 **/
1306 function spip_sqlite_demarrer_transaction($serveur = '', $requeter = true) {
1307 if (!$requeter) {
1308 return "BEGIN TRANSACTION";
1309 }
1310 spip_sqlite::demarrer_transaction($serveur);
1311
1312 return true;
1313 }
1314
1315 /**
1316 * Clôture une transaction
1317 *
1318 * @param string $serveur
1319 * Nom du connecteur
1320 * @param bool $requeter
1321 * true pour exécuter la requête ou false pour retourner le texte de la requête
1322 * @return bool|string
1323 * string si texte de la requête demandé, true sinon
1324 **/
1325 function spip_sqlite_terminer_transaction($serveur = '', $requeter = true) {
1326 if (!$requeter) {
1327 return "COMMIT";
1328 }
1329 spip_sqlite::finir_transaction($serveur);
1330
1331 return true;
1332 }
1333
1334
1335 /**
1336 * Liste les bases de données disponibles
1337 *
1338 * @param string $serveur
1339 * Nom du connecteur
1340 * @param bool $requeter
1341 * Inutilisé
1342 * @return array
1343 * Liste des noms de bases
1344 **/
1345 function spip_sqlite_listdbs($serveur = '', $requeter = true) {
1346 _sqlite_init();
1347
1348 if (!is_dir($d = substr(_DIR_DB, 0, -1))) {
1349 return array();
1350 }
1351
1352 include_spip('inc/flock');
1353 $bases = preg_files($d, $pattern = '(.*)\.sqlite$');
1354 $bds = array();
1355
1356 foreach ($bases as $b) {
1357 // pas de bases commencant pas sqlite
1358 // (on s'en sert pour l'installation pour simuler la presence d'un serveur)
1359 // les bases sont de la forme _sqliteX_tmp_spip_install.sqlite
1360 if (strpos($b, '_sqlite')) {
1361 continue;
1362 }
1363 $bds[] = preg_replace(";.*/$pattern;iS", '$1', $b);
1364 }
1365
1366 return $bds;
1367 }
1368
1369
1370 /**
1371 * Retourne l'instruction SQL pour obtenir le texte d'un champ contenant
1372 * une balise `<multi>` dans la langue indiquée
1373 *
1374 * Cette sélection est mise dans l'alias `multi` (instruction AS multi).
1375 *
1376 * @param string $objet Colonne ayant le texte
1377 * @param string $lang Langue à extraire
1378 * @return string Texte de sélection pour la requête
1379 */
1380 function spip_sqlite_multi($objet, $lang) {
1381 $r = "EXTRAIRE_MULTI(" . $objet . ", '" . $lang . "') AS multi";
1382
1383 return $r;
1384 }
1385
1386
1387 /**
1388 * Optimise une table SQL
1389 *
1390 * @note
1391 * Sqlite optimise TOUT un fichier sinon rien.
1392 * On évite donc 2 traitements sur la même base dans un hit.
1393 *
1394 * @param $table nom de la table a optimiser
1395 * @param $serveur nom de la connexion
1396 * @param $requeter effectuer la requete ? sinon retourner son code
1397 * @return bool|string true / false / requete
1398 **/
1399 function spip_sqlite_optimize($table, $serveur = '', $requeter = true) {
1400 static $do = false;
1401 if ($requeter and $do) {
1402 return true;
1403 }
1404 if ($requeter) {
1405 $do = true;
1406 }
1407
1408 return spip_sqlite_query("VACUUM", $serveur, $requeter);
1409 }
1410
1411
1412 /**
1413 * Échapper une valeur selon son type
1414 * mais pour SQLite avec ses spécificités
1415 *
1416 * @param string|array|number $v
1417 * Texte, nombre ou tableau à échapper
1418 * @param string $type
1419 * Description du type attendu
1420 * (par exemple description SQL de la colonne recevant la donnée)
1421 * @return string|number
1422 * Donnée prête à être utilisée par le gestionnaire SQL
1423 */
1424 function spip_sqlite_quote($v, $type = '') {
1425 if (!is_array($v)) {
1426 return _sqlite_calculer_cite($v, $type);
1427 }
1428 // si c'est un tableau, le parcourir en propageant le type
1429 foreach ($v as $k => $r) {
1430 $v[$k] = spip_sqlite_quote($r, $type);
1431 }
1432
1433 return join(",", $v);
1434 }
1435
1436
1437 /**
1438 * Tester si une date est proche de la valeur d'un champ
1439 *
1440 * @param string $champ
1441 * Nom du champ a tester
1442 * @param int $interval
1443 * Valeur de l'intervalle : -1, 4, ...
1444 * @param string $unite
1445 * Utité utilisée (DAY, MONTH, YEAR, ...)
1446 * @return string
1447 * Expression SQL
1448 **/
1449 function spip_sqlite_date_proche($champ, $interval, $unite) {
1450 $op = (($interval <= 0) ? '>' : '<');
1451
1452 return "($champ $op datetime('" . date("Y-m-d H:i:s") . "', '$interval $unite'))";
1453 }
1454
1455
1456 /**
1457 * Répare une table SQL
1458 *
1459 * Il n'y a pas de fonction native repair dans sqlite, mais on profite
1460 * pour vérifier que tous les champs (text|char) ont bien une clause DEFAULT
1461 *
1462 * @param string $table Nom de la table SQL
1463 * @param string $serveur Nom de la connexion
1464 * @param bool $requeter Exécuter la requête, sinon la retourner
1465 * @return string[]
1466 * Tableau avec clé 0 pouvant avoir " OK " ou " ERROR " indiquant
1467 * l'état de la table après la réparation
1468 */
1469 function spip_sqlite_repair($table, $serveur = '', $requeter = true) {
1470 if ($desc = spip_sqlite_showtable($table, $serveur)
1471 and isset($desc['field'])
1472 and is_array($desc['field'])
1473 ) {
1474 foreach ($desc['field'] as $c => $d) {
1475 if (preg_match(",^(tinytext|mediumtext|text|longtext|varchar|char),i", $d)
1476 and stripos($d, "NOT NULL") !== false
1477 and stripos($d, "DEFAULT") === false
1478 /* pas touche aux cles primaires */
1479 and (!isset($desc['key']['PRIMARY KEY']) or $desc['key']['PRIMARY KEY'] !== $c)
1480 ) {
1481 spip_sqlite_alter($q = "TABLE $table CHANGE $c $c $d DEFAULT ''", $serveur);
1482 spip_log("ALTER $q", "repair" . _LOG_INFO_IMPORTANTE);
1483 }
1484 if (preg_match(",^(INTEGER),i", $d)
1485 and stripos($d, "NOT NULL") !== false
1486 and stripos($d, "DEFAULT") === false
1487 /* pas touche aux cles primaires */
1488 and (!isset($desc['key']['PRIMARY KEY']) or $desc['key']['PRIMARY KEY'] !== $c)
1489 ) {
1490 spip_sqlite_alter($q = "TABLE $table CHANGE $c $c $d DEFAULT '0'", $serveur);
1491 spip_log("ALTER $q", "repair" . _LOG_INFO_IMPORTANTE);
1492 }
1493 if (preg_match(",^(datetime),i", $d)
1494 and stripos($d, "NOT NULL") !== false
1495 and stripos($d, "DEFAULT") === false
1496 /* pas touche aux cles primaires */
1497 and (!isset($desc['key']['PRIMARY KEY']) or $desc['key']['PRIMARY KEY'] !== $c)
1498 ) {
1499 spip_sqlite_alter($q = "TABLE $table CHANGE $c $c $d DEFAULT '0000-00-00 00:00:00'", $serveur);
1500 spip_log("ALTER $q", "repair" . _LOG_INFO_IMPORTANTE);
1501 }
1502 }
1503
1504 return array(" OK ");
1505 }
1506
1507 return array(" ERROR ");
1508 }
1509
1510
1511 /**
1512 * Insère où met à jour une entrée d’une table SQL
1513 *
1514 * La clé ou les cles primaires doivent être présentes dans les données insérés.
1515 * La fonction effectue une protection automatique des données.
1516 *
1517 * Préférer à cette fonction updateq ou insertq.
1518 *
1519 * @param string $table
1520 * Nom de la table SQL
1521 * @param array $couples
1522 * Couples colonne / valeur à modifier,
1523 * @param array $desc
1524 * Tableau de description des colonnes de la table SQL utilisée
1525 * (il sera calculé si nécessaire s'il n'est pas transmis).
1526 * @param string $serveur
1527 * Nom du connecteur
1528 * @param bool $requeter
1529 * Exécuter la requête, sinon la retourner
1530 * @return bool|string
1531 * - true si réussite
1532 * - Texte de la requête si demandé,
1533 * - False en cas d'erreur.
1534 **/
1535 function spip_sqlite_replace($table, $couples, $desc = array(), $serveur = '', $requeter = true) {
1536 if (!$desc) {
1537 $desc = description_table($table, $serveur);
1538 }
1539 if (!$desc) {
1540 die("$table insertion sans description");
1541 }
1542 $fields = isset($desc['field']) ? $desc['field'] : array();
1543
1544 foreach ($couples as $champ => $val) {
1545 $couples[$champ] = _sqlite_calculer_cite($val, $fields[$champ]);
1546 }
1547
1548 // recherche de champs 'timestamp' pour mise a jour auto de ceux-ci
1549 $couples = _sqlite_ajouter_champs_timestamp($table, $couples, $desc, $serveur);
1550
1551 return spip_sqlite_query("REPLACE INTO $table (" . join(',', array_keys($couples)) . ') VALUES (' . join(',',
1552 $couples) . ')', $serveur);
1553 }
1554
1555
1556 /**
1557 * Insère où met à jour des entrées d’une table SQL
1558 *
1559 * La clé ou les cles primaires doivent être présentes dans les données insérés.
1560 * La fonction effectue une protection automatique des données.
1561 *
1562 * Préférez insertq_multi et sql_updateq
1563 *
1564 * @param string $table
1565 * Nom de la table SQL
1566 * @param array $tab_couples
1567 * Tableau de tableau (colonne / valeur à modifier),
1568 * @param array $desc
1569 * Tableau de description des colonnes de la table SQL utilisée
1570 * (il sera calculé si nécessaire s'il n'est pas transmis).
1571 * @param string $serveur
1572 * Nom du connecteur
1573 * @param bool $requeter
1574 * Exécuter la requête, sinon la retourner
1575 * @return bool|string
1576 * - true si réussite
1577 * - Texte de la requête si demandé,
1578 * - False en cas d'erreur.
1579 **/
1580 function spip_sqlite_replace_multi($table, $tab_couples, $desc = array(), $serveur = '', $requeter = true) {
1581
1582 // boucler pour trainter chaque requete independemment
1583 foreach ($tab_couples as $couples) {
1584 $retour = spip_sqlite_replace($table, $couples, $desc, $serveur, $requeter);
1585 }
1586
1587 // renvoie le dernier id
1588 return $retour;
1589 }
1590
1591
1592 /**
1593 * Exécute une requête de sélection avec SQLite
1594 *
1595 * Instance de sql_select (voir ses specs).
1596 *
1597 * @see sql_select()
1598 *
1599 * @param string|array $select Champs sélectionnés
1600 * @param string|array $from Tables sélectionnées
1601 * @param string|array $where Contraintes
1602 * @param string|array $groupby Regroupements
1603 * @param string|array $orderby Tris
1604 * @param string $limit Limites de résultats
1605 * @param string|array $having Contraintes posts sélections
1606 * @param string $serveur Nom de la connexion
1607 * @param bool $requeter Exécuter la requête, sinon la retourner
1608 * @return array|bool|resource|string
1609 * - string : Texte de la requête si on ne l'exécute pas
1610 * - ressource si requête exécutée, ressource pour fetch()
1611 * - false si la requête exécutée a ratée
1612 * - array : Tableau décrivant requête et temps d'exécution si var_profile actif pour tracer.
1613 */
1614 function spip_sqlite_select(
1615 $select,
1616 $from,
1617 $where = '',
1618 $groupby = '',
1619 $orderby = '',
1620 $limit = '',
1621 $having = '',
1622 $serveur = '',
1623 $requeter = true
1624 ) {
1625
1626 // version() n'est pas connu de sqlite
1627 $select = str_replace('version()', 'sqlite_version()', $select);
1628
1629 // recomposer from
1630 $from = (!is_array($from) ? $from : _sqlite_calculer_select_as($from));
1631
1632 $query =
1633 _sqlite_calculer_expression('SELECT', $select, ', ')
1634 . _sqlite_calculer_expression('FROM', $from, ', ')
1635 . _sqlite_calculer_expression('WHERE', $where)
1636 . _sqlite_calculer_expression('GROUP BY', $groupby, ',')
1637 . _sqlite_calculer_expression('HAVING', $having)
1638 . ($orderby ? ("\nORDER BY " . _sqlite_calculer_order($orderby)) : '')
1639 . ($limit ? "\nLIMIT $limit" : '');
1640
1641 // dans un select, on doit renvoyer la requête en cas d'erreur
1642 $res = spip_sqlite_query($query, $serveur, $requeter);
1643 // texte de la requete demande ?
1644 if (!$requeter) {
1645 return $res;
1646 }
1647 // erreur survenue ?
1648 if ($res === false) {
1649 return spip_sqlite::traduire_requete($query, $serveur);
1650 }
1651
1652 return $res;
1653 }
1654
1655
1656 /**
1657 * Sélectionne un fichier de base de données
1658 *
1659 * @param string $db
1660 * Nom de la base à utiliser
1661 * @param string $serveur
1662 * Nom du connecteur
1663 * @param bool $requeter
1664 * Inutilisé
1665 *
1666 * @return bool|string
1667 * - Nom de la base en cas de success.
1668 * - False en cas d'erreur.
1669 **/
1670 function spip_sqlite_selectdb($db, $serveur = '', $requeter = true) {
1671 _sqlite_init();
1672
1673 // interdire la creation d'une nouvelle base,
1674 // sauf si on est dans l'installation
1675 if (!is_file($f = _DIR_DB . $db . '.sqlite')
1676 && (!defined('_ECRIRE_INSTALL') || !_ECRIRE_INSTALL)
1677 ) {
1678 spip_log("Il est interdit de creer la base $db", 'sqlite.' . _LOG_HS);
1679
1680 return false;
1681 }
1682
1683 // se connecter a la base indiquee
1684 // avec les identifiants connus
1685 $index = $serveur ? $serveur : 0;
1686
1687 if ($link = spip_connect_db('', '', '', '', '@selectdb@' . $db, $serveur, '', '')) {
1688 if (($db == $link['db']) && $GLOBALS['connexions'][$index] = $link) {
1689 return $db;
1690 }
1691 } else {
1692 spip_log("Impossible de selectionner la base $db", 'sqlite.' . _LOG_HS);
1693
1694 return false;
1695 }
1696
1697 }
1698
1699
1700 /**
1701 * Définit un charset pour la connexion avec SQLite (aucune action ici)
1702 *
1703 * Cette fonction n'a aucune action actuellement.
1704 *
1705 * @param string $charset Charset à appliquer
1706 * @param string $serveur Nom de la connexion
1707 * @param bool $requeter inutilisé
1708 * @return void
1709 */
1710 function spip_sqlite_set_charset($charset, $serveur = '', $requeter = true) {
1711 # spip_log("Gestion charset sql a ecrire : "."SET NAMES "._q($charset), 'sqlite.'._LOG_ERREUR);
1712 # return spip_sqlite_query("SET NAMES ". spip_sqlite_quote($charset), $serveur); //<-- Passe pas !
1713 }
1714
1715
1716 /**
1717 * Retourne une ressource de la liste des tables de la base de données
1718 *
1719 * @param string $match
1720 * Filtre sur tables à récupérer
1721 * @param string $serveur
1722 * Connecteur de la base
1723 * @param bool $requeter
1724 * true pour éxecuter la requête
1725 * false pour retourner le texte de la requête.
1726 * @return ressource
1727 * Ressource à utiliser avec sql_fetch()
1728 **/
1729 function spip_sqlite_showbase($match, $serveur = '', $requeter = true) {
1730 // type est le type d'entrée : table / index / view
1731 // on ne retourne que les tables (?) et non les vues...
1732 # ESCAPE non supporte par les versions sqlite <3
1733 # return spip_sqlite_query("SELECT name FROM sqlite_master WHERE type='table' AND tbl_name LIKE "._q($match)." ESCAPE '\'", $serveur, $requeter);
1734 $match = preg_quote($match);
1735 $match = str_replace("\\\_", "[[TIRETBAS]]", $match);
1736 $match = str_replace("\\\%", "[[POURCENT]]", $match);
1737 $match = str_replace("_", ".", $match);
1738 $match = str_replace("%", ".*", $match);
1739 $match = str_replace("[[TIRETBAS]]", "_", $match);
1740 $match = str_replace("[[POURCENT]]", "%", $match);
1741 $match = "^$match$";
1742
1743 return spip_sqlite_query("SELECT name FROM sqlite_master WHERE type='table' AND tbl_name REGEXP " . _q($match),
1744 $serveur, $requeter);
1745 }
1746
1747 define('_SQLITE_RE_SHOW_TABLE', '/^[^(),]*\(((?:[^()]*\((?:[^()]*\([^()]*\))?[^()]*\)[^()]*)*[^()]*)\)[^()]*$/');
1748 /**
1749 * Obtient la description d'une table ou vue SQLite
1750 *
1751 * Récupère la définition d'une table ou d'une vue avec colonnes, indexes, etc.
1752 * au même format que la définition des tables SPIP, c'est à dire
1753 * un tableau avec les clés
1754 *
1755 * - `field` (tableau colonne => description SQL) et
1756 * - `key` (tableau type de clé => colonnes)
1757 *
1758 * @param string $nom_table Nom de la table SQL
1759 * @param string $serveur Nom de la connexion
1760 * @param bool $requeter Exécuter la requête, sinon la retourner
1761 * @return array|string
1762 * - chaîne vide si pas de description obtenue
1763 * - string Texte de la requête si demandé
1764 * - array description de la table sinon
1765 */
1766 function spip_sqlite_showtable($nom_table, $serveur = '', $requeter = true) {
1767 $query =
1768 'SELECT sql, type FROM'
1769 . ' (SELECT * FROM sqlite_master UNION ALL'
1770 . ' SELECT * FROM sqlite_temp_master)'
1771 . " WHERE tbl_name LIKE '$nom_table'"
1772 . " AND type!='meta' AND sql NOT NULL AND name NOT LIKE 'sqlite_%'"
1773 . ' ORDER BY substr(type,2,1), name';
1774
1775 $a = spip_sqlite_query($query, $serveur, $requeter);
1776 if (!$a) {
1777 return "";
1778 }
1779 if (!$requeter) {
1780 return $a;
1781 }
1782 if (!($a = spip_sqlite_fetch($a, null, $serveur))) {
1783 return "";
1784 }
1785 $vue = ($a['type'] == 'view'); // table | vue
1786
1787 // c'est une table
1788 // il faut parser le create
1789 if (!$vue) {
1790 if (!preg_match(_SQLITE_RE_SHOW_TABLE, array_shift($a), $r)) {
1791 return "";
1792 } else {
1793 $desc = $r[1];
1794 // extraction d'une KEY éventuelle en prenant garde de ne pas
1795 // relever un champ dont le nom contient KEY (ex. ID_WHISKEY)
1796 if (preg_match("/^(.*?),([^,]*\sKEY[ (].*)$/s", $desc, $r)) {
1797 $namedkeys = $r[2];
1798 $desc = $r[1];
1799 } else {
1800 $namedkeys = "";
1801 }
1802
1803 $fields = array();
1804 $keys = array();
1805
1806 // enlever les contenus des valeurs DEFAULT 'xxx' qui pourraient perturber
1807 // par exemple s'il contiennent une virgule.
1808 // /!\ cela peut aussi echapper le nom des champs si la table a eu des operations avec SQLite Manager !
1809 list($desc, $echaps) = query_echappe_textes($desc);
1810
1811 // separer toutes les descriptions de champs, separes par des virgules
1812 # /!\ explode peut exploser aussi DECIMAL(10,2) !
1813 $k_precedent = null;
1814 foreach (explode(",", $desc) as $v) {
1815
1816 preg_match("/^\s*([^\s]+)\s+(.*)/", $v, $r);
1817 // Les cles de champs peuvent etre entourees
1818 // de guillements doubles " , simples ', graves ` ou de crochets [ ], ou rien.
1819 // http://www.sqlite.org/lang_keywords.html
1820 $k = strtolower(query_reinjecte_textes($r[1], $echaps)); // champ, "champ", [champ]...
1821 if ($char = strpbrk($k[0], '\'"[`')) {
1822 $k = trim($k, $char);
1823 if ($char == '[') {
1824 $k = rtrim($k, ']');
1825 }
1826 }
1827 $def = query_reinjecte_textes($r[2], $echaps); // valeur du champ
1828
1829 # rustine pour DECIMAL(10,2)
1830 if (false !== strpos($k, ')')) {
1831 $fields[$k_precedent] .= ',' . $k . ' ' . $def;
1832 continue;
1833 }
1834
1835 $fields[$k] = $def;
1836 $k_precedent = $k;
1837
1838 // la primary key peut etre dans une des descriptions de champs
1839 // et non en fin de table, cas encore decouvert avec Sqlite Manager
1840 if (stripos($r[2], 'PRIMARY KEY') !== false) {
1841 $keys['PRIMARY KEY'] = $k;
1842 }
1843 }
1844 // key inclues dans la requete
1845 foreach (preg_split('/\)\s*(,|$)/', $namedkeys) as $v) {
1846 if (preg_match("/^\s*([^(]*)\(([^(]*(\(\d+\))?)$/", $v, $r)) {
1847 $k = str_replace("`", '', trim($r[1]));
1848 $t = trim(strtolower(str_replace("`", '', $r[2])), '"');
1849 if ($k && !isset($keys[$k])) {
1850 $keys[$k] = $t;
1851 } else {
1852 $keys[] = $t;
1853 }
1854 }
1855 }
1856 // sinon ajouter les key index
1857 $query =
1858 'SELECT name,sql FROM'
1859 . ' (SELECT * FROM sqlite_master UNION ALL'
1860 . ' SELECT * FROM sqlite_temp_master)'
1861 . " WHERE tbl_name LIKE '$nom_table'"
1862 . " AND type='index' AND name NOT LIKE 'sqlite_%'"
1863 . 'ORDER BY substr(type,2,1), name';
1864 $a = spip_sqlite_query($query, $serveur, $requeter);
1865 while ($r = spip_sqlite_fetch($a, null, $serveur)) {
1866 $key = str_replace($nom_table . '_', '', $r['name']); // enlever le nom de la table ajoute a l'index
1867 $keytype = "KEY";
1868 if (strpos($r['sql'], "UNIQUE INDEX") !== false) {
1869 $keytype = "UNIQUE KEY";
1870 }
1871 $colonnes = preg_replace(',.*\((.*)\).*,', '$1', $r['sql']);
1872 $keys[$keytype . ' ' . $key] = $colonnes;
1873 }
1874 }
1875
1876 } // c'est une vue, on liste les champs disponibles simplement
1877 else {
1878 if ($res = sql_fetsel('*', $nom_table, '', '', '', '1', '', $serveur)) { // limit 1
1879 $fields = array();
1880 foreach ($res as $c => $v) {
1881 $fields[$c] = '';
1882 }
1883 $keys = array();
1884 } else {
1885 return "";
1886 }
1887 }
1888
1889 return array('field' => $fields, 'key' => $keys);
1890
1891 }
1892
1893
1894 /**
1895 * Met à jour des enregistrements d'une table SQL
1896 *
1897 * @param string $table
1898 * Nom de la table
1899 * @param array $champs
1900 * Couples (colonne => valeur)
1901 * @param string|array $where
1902 * Conditions a remplir (Where)
1903 * @param array $desc
1904 * Tableau de description des colonnes de la table SQL utilisée
1905 * (il sera calculé si nécessaire s'il n'est pas transmis).
1906 * @param string $serveur
1907 * Nom de la connexion
1908 * @param bool $requeter
1909 * Exécuter la requête, sinon la retourner
1910 * @return array|bool|string
1911 * - string : texte de la requête si demandé
1912 * - true si la requête a réussie, false sinon
1913 * - array Tableau décrivant la requête et son temps d'exécution si var_profile est actif
1914 */
1915 function spip_sqlite_update($table, $champs, $where = '', $desc = '', $serveur = '', $requeter = true) {
1916 // recherche de champs 'timestamp' pour mise a jour auto de ceux-ci
1917 $champs = _sqlite_ajouter_champs_timestamp($table, $champs, $desc, $serveur);
1918
1919 $set = array();
1920 foreach ($champs as $champ => $val) {
1921 $set[] = $champ . "=$val";
1922 }
1923 if (!empty($set)) {
1924 return spip_sqlite_query(
1925 _sqlite_calculer_expression('UPDATE', $table, ',')
1926 . _sqlite_calculer_expression('SET', $set, ',')
1927 . _sqlite_calculer_expression('WHERE', $where),
1928 $serveur, $requeter);
1929 }
1930 }
1931
1932
1933 /**
1934 * Met à jour des enregistrements d'une table SQL et protège chaque valeur
1935 *
1936 * Protège chaque valeur transmise avec sql_quote(), adapté au type
1937 * de champ attendu par la table SQL
1938 *
1939 * @param string $table
1940 * Nom de la table
1941 * @param array $champs
1942 * Couples (colonne => valeur)
1943 * @param string|array $where
1944 * Conditions a remplir (Where)
1945 * @param array $desc
1946 * Tableau de description des colonnes de la table SQL utilisée
1947 * (il sera calculé si nécessaire s'il n'est pas transmis).
1948 * @param string $serveur
1949 * Nom de la connexion
1950 * @param bool $requeter
1951 * Exécuter la requête, sinon la retourner
1952 * @return array|bool|string
1953 * - string : texte de la requête si demandé
1954 * - true si la requête a réussie, false sinon
1955 * - array Tableau décrivant la requête et son temps d'exécution si var_profile est actif
1956 */
1957 function spip_sqlite_updateq($table, $champs, $where = '', $desc = array(), $serveur = '', $requeter = true) {
1958
1959 if (!$champs) {
1960 return;
1961 }
1962 if (!$desc) {
1963 $desc = description_table($table, $serveur);
1964 }
1965 if (!$desc) {
1966 die("$table insertion sans description");
1967 }
1968 $fields = $desc['field'];
1969
1970 // recherche de champs 'timestamp' pour mise a jour auto de ceux-ci
1971 $champs = _sqlite_ajouter_champs_timestamp($table, $champs, $desc, $serveur);
1972
1973 $set = array();
1974 foreach ($champs as $champ => $val) {
1975 $set[] = $champ . '=' . _sqlite_calculer_cite($val, isset($fields[$champ]) ? $fields[$champ] : '');
1976 }
1977
1978 return spip_sqlite_query(
1979 _sqlite_calculer_expression('UPDATE', $table, ',')
1980 . _sqlite_calculer_expression('SET', $set, ',')
1981 . _sqlite_calculer_expression('WHERE', $where),
1982 $serveur, $requeter);
1983 }
1984
1985
1986 /*
1987 *
1988 * Ensuite les fonctions non abstraites
1989 * crees pour l'occasion de sqlite
1990 *
1991 */
1992
1993
1994 /**
1995 * Initialise la première connexion à un serveur SQLite
1996 *
1997 * @return void
1998 */
1999 function _sqlite_init() {
2000 if (!defined('_DIR_DB')) {
2001 define('_DIR_DB', _DIR_ETC . 'bases/');
2002 }
2003 if (!defined('_SQLITE_CHMOD')) {
2004 define('_SQLITE_CHMOD', _SPIP_CHMOD);
2005 }
2006
2007 if (!is_dir($d = _DIR_DB)) {
2008 include_spip('inc/flock');
2009 sous_repertoire($d);
2010 }
2011 }
2012
2013
2014 /**
2015 * Teste la version sqlite du link en cours
2016 *
2017 * @param string $version
2018 * @param string $link
2019 * @param string $serveur
2020 * @param bool $requeter
2021 * @return bool|int
2022 */
2023 function _sqlite_is_version($version = '', $link = '', $serveur = '', $requeter = true) {
2024 if ($link === '') {
2025 $link = _sqlite_link($serveur);
2026 }
2027 if (!$link) {
2028 return false;
2029 }
2030 if ($link instanceof PDO) {
2031 $v = 3;
2032 } else {
2033 $v = 2;
2034 }
2035
2036 if (!$version) {
2037 return $v;
2038 }
2039
2040 return ($version == $v);
2041 }
2042
2043
2044 /**
2045 * Retrouver un link d'une connexion SQLite
2046 *
2047 * @param string $serveur Nom du serveur
2048 * @return Object Information de connexion pour SQLite
2049 */
2050 function _sqlite_link($serveur = '') {
2051 $link = &$GLOBALS['connexions'][$serveur ? $serveur : 0]['link'];
2052
2053 return $link;
2054 }
2055
2056
2057 /* ordre alphabetique pour les autres */
2058
2059
2060 /**
2061 * Renvoie les bons echappements (mais pas sur les fonctions comme NOW())
2062 *
2063 * @param string|number $v Texte ou nombre à échapper
2064 * @param string $type Type de donnée attendue, description SQL de la colonne de destination
2065 * @return string|number Texte ou nombre échappé
2066 */
2067 function _sqlite_calculer_cite($v, $type) {
2068 if ($type) {
2069 if (is_null($v)
2070 and stripos($type, "NOT NULL") === false
2071 ) {
2072 return 'NULL';
2073 } // null php se traduit en NULL SQL
2074
2075 if (sql_test_date($type) and preg_match('/^\w+\(/', $v)) {
2076 return $v;
2077 }
2078 if (sql_test_int($type)) {
2079 if (is_numeric($v)) {
2080 return $v;
2081 } elseif (ctype_xdigit(substr($v, 2)) and strncmp($v, '0x', 2) == 0) {
2082 return hexdec(substr($v, 2));
2083 } else {
2084 return intval($v);
2085 }
2086 }
2087 } else {
2088 // si on ne connait pas le type on le deduit de $v autant que possible
2089 if (is_numeric($v)) {
2090 return strval($v);
2091 }
2092 }
2093
2094 if (function_exists('sqlite_escape_string')) {
2095 return "'" . sqlite_escape_string($v) . "'";
2096 }
2097
2098 // trouver un link sqlite3 pour faire l'echappement
2099 foreach ($GLOBALS['connexions'] as $s) {
2100 if (_sqlite_is_version(3, $l = $s['link'])) {
2101 return $l->quote($v);
2102 }
2103 }
2104
2105 // echapper les ' en ''
2106 spip_log("Pas de methode sqlite_escape_string ni ->quote pour echapper", "sqlite." . _LOG_INFO_IMPORTANTE);
2107
2108 return ("'" . str_replace("'", "''", $v) . "'");
2109 }
2110
2111
2112 /**
2113 * Calcule un expression pour une requête, en cumulant chaque élément
2114 * avec l'opérateur de liaison ($join) indiqué
2115 *
2116 * Renvoie grosso modo "$expression join($join, $v)"
2117 *
2118 * @param string $expression Mot clé de l'expression, tel que "WHERE" ou "ORDER BY"
2119 * @param array|string $v Données de l'expression
2120 * @param string $join Si les données sont un tableau, elles seront groupées par cette jointure
2121 * @return string Texte de l'expression, une partie donc, du texte la requête.
2122 */
2123 function _sqlite_calculer_expression($expression, $v, $join = 'AND') {
2124 if (empty($v)) {
2125 return '';
2126 }
2127
2128 $exp = "\n$expression ";
2129
2130 if (!is_array($v)) {
2131 return $exp . $v;
2132 } else {
2133 if (strtoupper($join) === 'AND') {
2134 return $exp . join("\n\t$join ", array_map('_sqlite_calculer_where', $v));
2135 } else {
2136 return $exp . join($join, $v);
2137 }
2138 }
2139 }
2140
2141
2142 /**
2143 * Prépare une clause order by
2144 *
2145 * Regroupe en texte les éléments si un tableau est donné
2146 *
2147 * @note
2148 * Pas besoin de conversion pour 0+x comme il faudrait pour mysql.
2149 *
2150 * @param string|array $orderby Texte du orderby à préparer
2151 * @return string Texte du orderby préparé
2152 */
2153 function _sqlite_calculer_order($orderby) {
2154 return (is_array($orderby)) ? join(", ", $orderby) : $orderby;
2155 }
2156
2157
2158 /**
2159 * Renvoie des `nom AS alias`
2160 *
2161 * @param array $args
2162 * @return string Sélection de colonnes pour une clause SELECT
2163 */
2164 function _sqlite_calculer_select_as($args) {
2165 $res = '';
2166 foreach ($args as $k => $v) {
2167 if (substr($k, -1) == '@') {
2168 // c'est une jointure qui se refere au from precedent
2169 // pas de virgule
2170 $res .= ' ' . $v;
2171 } else {
2172 if (!is_numeric($k)) {
2173 $p = strpos($v, " ");
2174 if ($p) {
2175 $v = substr($v, 0, $p) . " AS '$k'" . substr($v, $p);
2176 } else {
2177 $v .= " AS '$k'";
2178 }
2179 }
2180 $res .= ', ' . $v;
2181 }
2182 }
2183
2184 return substr($res, 2);
2185 }
2186
2187
2188 /**
2189 * Prépare une clause WHERE pour SQLite
2190 *
2191 * Retourne une chaîne avec les bonnes parenthèses pour la
2192 * contrainte indiquée, au format donnée par le compilateur
2193 *
2194 * @param array|string $v
2195 * Description des contraintes
2196 * - string : Texte du where
2197 * - sinon tableau : A et B peuvent être de type string ou array,
2198 * OP et C sont de type string :
2199 * - array(A) : A est le texte du where
2200 * - array(OP, A) : contrainte OP( A )
2201 * - array(OP, A, B) : contrainte (A OP B)
2202 * - array(OP, A, B, C) : contrainte (A OP (B) : C)
2203 * @return string
2204 * Contrainte pour clause WHERE
2205 */
2206 function _sqlite_calculer_where($v) {
2207 if (!is_array($v)) {
2208 return $v;
2209 }
2210
2211 $op = array_shift($v);
2212 if (!($n = count($v))) {
2213 return $op;
2214 } else {
2215 $arg = _sqlite_calculer_where(array_shift($v));
2216 if ($n == 1) {
2217 return "$op($arg)";
2218 } else {
2219 $arg2 = _sqlite_calculer_where(array_shift($v));
2220 if ($n == 2) {
2221 return "($arg $op $arg2)";
2222 } else {
2223 return "($arg $op ($arg2) : $v[0])";
2224 }
2225 }
2226 }
2227 }
2228
2229
2230 /**
2231 * Charger les modules SQLite
2232 *
2233 * Si possible et juste la version demandée,
2234 * ou, si aucune version, on renvoie les versions sqlite disponibles
2235 * sur ce serveur dans un tableau
2236 *
2237 * @param string $version
2238 * @return array|bool
2239 */
2240 function _sqlite_charger_version($version = '') {
2241 $versions = array();
2242
2243 // version 2
2244 if (!$version || $version == 2) {
2245 if (charger_php_extension('sqlite')) {
2246 $versions[] = 2;
2247 }
2248 }
2249
2250 // version 3
2251 if (!$version || $version == 3) {
2252 if (charger_php_extension('pdo') && charger_php_extension('pdo_sqlite')) {
2253 $versions[] = 3;
2254 }
2255 }
2256 if ($version) {
2257 return in_array($version, $versions);
2258 }
2259
2260 return $versions;
2261 }
2262
2263
2264 /**
2265 * Gestion des requêtes ALTER non reconnues de SQLite
2266 *
2267 * Requêtes non reconnues :
2268 *
2269 * ALTER TABLE table DROP column
2270 * ALTER TABLE table CHANGE [COLUMN] columnA columnB definition
2271 * ALTER TABLE table MODIFY column definition
2272 * ALTER TABLE table ADD|DROP PRIMARY KEY
2273 *
2274 * `MODIFY` est transformé en `CHANGE columnA columnA` par spip_sqlite_alter()
2275 *
2276 * 1) Créer une table B avec le nouveau format souhaité
2277 * 2) Copier la table d'origine A vers B
2278 * 3) Supprimer la table A
2279 * 4) Renommer la table B en A
2280 * 5) Remettre les index (qui sont supprimés avec la table A)
2281 *
2282 * @param string|array $table
2283 * - string : Nom de la table table,
2284 * - array : couple (nom de la table => nom futur)
2285 * @param string|array $colonne
2286 * - string : nom de la colonne,
2287 * - array : couple (nom de la colonne => nom futur)
2288 * @param array $opt
2289 * options comme les tables SPIP, qui sera mergé à la table créee :
2290 * `array('field'=>array('nom'=>'syntaxe', ...), 'key'=>array('KEY nom'=>'colonne', ...))`
2291 * @param string $serveur
2292 * Nom de la connexion SQL en cours
2293 * @return bool
2294 * true si OK, false sinon.
2295 */
2296 function _sqlite_modifier_table($table, $colonne, $opt = array(), $serveur = '') {
2297
2298 if (is_array($table)) {
2299 reset($table);
2300 list($table_origine, $table_destination) = each($table);
2301 } else {
2302 $table_origine = $table_destination = $table;
2303 }
2304 // ne prend actuellement qu'un changement
2305 // mais pourra etre adapte pour changer plus qu'une colonne a la fois
2306 if (is_array($colonne)) {
2307 reset($colonne);
2308 list($colonne_origine, $colonne_destination) = each($colonne);
2309 } else {
2310 $colonne_origine = $colonne_destination = $colonne;
2311 }
2312 if (!isset($opt['field'])) {
2313 $opt['field'] = array();
2314 }
2315 if (!isset($opt['key'])) {
2316 $opt['key'] = array();
2317 }
2318
2319 // si les noms de tables sont differents, pas besoin de table temporaire
2320 // on prendra directement le nom de la future table
2321 $meme_table = ($table_origine == $table_destination);
2322
2323 $def_origine = sql_showtable($table_origine, false, $serveur);
2324 if (!$def_origine or !isset($def_origine['field'])) {
2325 spip_log("Alter table impossible sur $table_origine : table non trouvee", 'sqlite' . _LOG_ERREUR);
2326
2327 return false;
2328 }
2329
2330
2331 $table_tmp = $table_origine . '_tmp';
2332
2333 // 1) creer une table temporaire avec les modifications
2334 // - DROP : suppression de la colonne
2335 // - CHANGE : modification de la colonne
2336 // (foreach pour conserver l'ordre des champs)
2337
2338 // field
2339 $fields = array();
2340 // pour le INSERT INTO plus loin
2341 // stocker la correspondance nouvelles->anciennes colonnes
2342 $fields_correspondances = array();
2343 foreach ($def_origine['field'] as $c => $d) {
2344
2345 if ($colonne_origine && ($c == $colonne_origine)) {
2346 // si pas DROP
2347 if ($colonne_destination) {
2348 $fields[$colonne_destination] = $opt['field'][$colonne_destination];
2349 $fields_correspondances[$colonne_destination] = $c;
2350 }
2351 } else {
2352 $fields[$c] = $d;
2353 $fields_correspondances[$c] = $c;
2354 }
2355 }
2356 // cas de ADD sqlite2 (ajout du champ en fin de table):
2357 if (!$colonne_origine && $colonne_destination) {
2358 $fields[$colonne_destination] = $opt['field'][$colonne_destination];
2359 }
2360
2361 // key...
2362 $keys = array();
2363 foreach ($def_origine['key'] as $c => $d) {
2364 $c = str_replace($colonne_origine, $colonne_destination, $c);
2365 $d = str_replace($colonne_origine, $colonne_destination, $d);
2366 // seulement si on ne supprime pas la colonne !
2367 if ($d) {
2368 $keys[$c] = $d;
2369 }
2370 }
2371
2372 // autres keys, on merge
2373 $keys = array_merge($keys, $opt['key']);
2374 $queries = array();
2375
2376 // copier dans destination (si differente de origine), sinon tmp
2377 $table_copie = ($meme_table) ? $table_tmp : $table_destination;
2378 $autoinc = (isset($keys['PRIMARY KEY'])
2379 and $keys['PRIMARY KEY']
2380 and stripos($keys['PRIMARY KEY'], ',') === false
2381 and stripos($fields[$keys['PRIMARY KEY']], 'default') === false);
2382
2383 if ($q = _sqlite_requete_create(
2384 $table_copie,
2385 $fields,
2386 $keys,
2387 $autoinc,
2388 $temporary = false,
2389 $ifnotexists = true,
2390 $serveur)
2391 ) {
2392 $queries[] = $q;
2393 }
2394
2395
2396 // 2) y copier les champs qui vont bien
2397 $champs_dest = join(', ', array_keys($fields_correspondances));
2398 $champs_ori = join(', ', $fields_correspondances);
2399 $queries[] = "INSERT INTO $table_copie ($champs_dest) SELECT $champs_ori FROM $table_origine";
2400
2401 // 3) supprimer la table d'origine
2402 $queries[] = "DROP TABLE $table_origine";
2403
2404 // 4) renommer la table temporaire
2405 // avec le nom de la table destination
2406 // si necessaire
2407 if ($meme_table) {
2408 if (_sqlite_is_version(3, '', $serveur)) {
2409 $queries[] = "ALTER TABLE $table_copie RENAME TO $table_destination";
2410 } else {
2411 $queries[] = _sqlite_requete_create(
2412 $table_destination,
2413 $fields,
2414 $keys,
2415 $autoinc,
2416 $temporary = false,
2417 $ifnotexists = false, // la table existe puisqu'on est dans une transaction
2418 $serveur);
2419 $queries[] = "INSERT INTO $table_destination SELECT * FROM $table_copie";
2420 $queries[] = "DROP TABLE $table_copie";
2421 }
2422 }
2423
2424 // 5) remettre les index !
2425 foreach ($keys as $k => $v) {
2426 if ($k == 'PRIMARY KEY') {
2427 } else {
2428 // enlever KEY
2429 $k = substr($k, 4);
2430 $queries[] = "CREATE INDEX $table_destination" . "_$k ON $table_destination ($v)";
2431 }
2432 }
2433
2434
2435 if (count($queries)) {
2436 spip_sqlite::demarrer_transaction($serveur);
2437 // il faut les faire une par une car $query = join('; ', $queries).";"; ne fonctionne pas
2438 foreach ($queries as $q) {
2439 if (!spip_sqlite::executer_requete($q, $serveur)) {
2440 spip_log(_LOG_GRAVITE_ERREUR, "SQLite : ALTER TABLE table :"
2441 . " Erreur a l'execution de la requete : $q", 'sqlite');
2442 spip_sqlite::annuler_transaction($serveur);
2443
2444 return false;
2445 }
2446 }
2447 spip_sqlite::finir_transaction($serveur);
2448 }
2449
2450 return true;
2451 }
2452
2453
2454 /**
2455 * Nom des fonctions
2456 *
2457 * @return array
2458 */
2459 function _sqlite_ref_fonctions() {
2460 $fonctions = array(
2461 'alter' => 'spip_sqlite_alter',
2462 'count' => 'spip_sqlite_count',
2463 'countsel' => 'spip_sqlite_countsel',
2464 'create' => 'spip_sqlite_create',
2465 'create_base' => 'spip_sqlite_create_base',
2466 'create_view' => 'spip_sqlite_create_view',
2467 'date_proche' => 'spip_sqlite_date_proche',
2468 'delete' => 'spip_sqlite_delete',
2469 'drop_table' => 'spip_sqlite_drop_table',
2470 'drop_view' => 'spip_sqlite_drop_view',
2471 'errno' => 'spip_sqlite_errno',
2472 'error' => 'spip_sqlite_error',
2473 'explain' => 'spip_sqlite_explain',
2474 'fetch' => 'spip_sqlite_fetch',
2475 'seek' => 'spip_sqlite_seek',
2476 'free' => 'spip_sqlite_free',
2477 'hex' => 'spip_sqlite_hex',
2478 'in' => 'spip_sqlite_in',
2479 'insert' => 'spip_sqlite_insert',
2480 'insertq' => 'spip_sqlite_insertq',
2481 'insertq_multi' => 'spip_sqlite_insertq_multi',
2482 'listdbs' => 'spip_sqlite_listdbs',
2483 'multi' => 'spip_sqlite_multi',
2484 'optimize' => 'spip_sqlite_optimize',
2485 'query' => 'spip_sqlite_query',
2486 'quote' => 'spip_sqlite_quote',
2487 'repair' => 'spip_sqlite_repair',
2488 'replace' => 'spip_sqlite_replace',
2489 'replace_multi' => 'spip_sqlite_replace_multi',
2490 'select' => 'spip_sqlite_select',
2491 'selectdb' => 'spip_sqlite_selectdb',
2492 'set_charset' => 'spip_sqlite_set_charset',
2493 'get_charset' => 'spip_sqlite_get_charset',
2494 'showbase' => 'spip_sqlite_showbase',
2495 'showtable' => 'spip_sqlite_showtable',
2496 'update' => 'spip_sqlite_update',
2497 'updateq' => 'spip_sqlite_updateq',
2498 'preferer_transaction' => 'spip_sqlite_preferer_transaction',
2499 'demarrer_transaction' => 'spip_sqlite_demarrer_transaction',
2500 'terminer_transaction' => 'spip_sqlite_terminer_transaction',
2501 );
2502
2503 // association de chaque nom http d'un charset aux couples sqlite
2504 // SQLite supporte utf-8 et utf-16 uniquement.
2505 $charsets = array(
2506 'utf-8' => array('charset' => 'utf8', 'collation' => 'utf8_general_ci'),
2507 //'utf-16be'=>array('charset'=>'utf16be','collation'=>'UTF-16BE'),// aucune idee de quoi il faut remplir dans es champs la
2508 //'utf-16le'=>array('charset'=>'utf16le','collation'=>'UTF-16LE')
2509 );
2510
2511 $fonctions['charsets'] = $charsets;
2512
2513 return $fonctions;
2514 }
2515
2516
2517 /**
2518 * $query est une requete ou une liste de champs
2519 *
2520 * @param $query
2521 * @param bool $autoinc
2522 * @return mixed
2523 */
2524 function _sqlite_remplacements_definitions_table($query, $autoinc = false) {
2525 // quelques remplacements
2526 $num = "(\s*\([0-9]*\))?";
2527 $enum = "(\s*\([^\)]*\))?";
2528
2529 $remplace = array(
2530 '/enum' . $enum . '/is' => 'VARCHAR(255)',
2531 '/COLLATE \w+_bin/is' => 'COLLATE BINARY',
2532 '/COLLATE \w+_ci/is' => 'COLLATE NOCASE',
2533 '/auto_increment/is' => '',
2534 '/(timestamp .* )ON .*$/is' => '\\1',
2535 '/character set \w+/is' => '',
2536 '/((big|small|medium|tiny)?int(eger)?)' . $num . '\s*unsigned/is' => '\\1 UNSIGNED',
2537 '/(text\s+not\s+null(\s+collate\s+\w+)?)\s*$/is' => "\\1 DEFAULT ''",
2538 '/((char|varchar)' . $num . '\s+not\s+null(\s+collate\s+\w+)?)\s*$/is' => "\\1 DEFAULT ''",
2539 '/(datetime\s+not\s+null)\s*$/is' => "\\1 DEFAULT '0000-00-00 00:00:00'",
2540 '/(date\s+not\s+null)\s*$/is' => "\\1 DEFAULT '0000-00-00'",
2541 );
2542
2543 // pour l'autoincrement, il faut des INTEGER NOT NULL PRIMARY KEY
2544 $remplace_autocinc = array(
2545 '/(big|small|medium|tiny)?int(eger)?' . $num . '/is' => 'INTEGER'
2546 );
2547 // pour les int non autoincrement, il faut un DEFAULT
2548 $remplace_nonautocinc = array(
2549 '/((big|small|medium|tiny)?int(eger)?' . $num . '\s+not\s+null)\s*$/is' => "\\1 DEFAULT 0",
2550 );
2551
2552 if (is_string($query)) {
2553 $query = preg_replace(array_keys($remplace), $remplace, $query);
2554 if ($autoinc or preg_match(',AUTO_INCREMENT,is', $query)) {
2555 $query = preg_replace(array_keys($remplace_autocinc), $remplace_autocinc, $query);
2556 } else {
2557 $query = preg_replace(array_keys($remplace_nonautocinc), $remplace_nonautocinc, $query);
2558 $query = _sqlite_collate_ci($query);
2559 }
2560 } elseif (is_array($query)) {
2561 foreach ($query as $k => $q) {
2562 $ai = ($autoinc ? $k == $autoinc : preg_match(',AUTO_INCREMENT,is', $q));
2563 $query[$k] = preg_replace(array_keys($remplace), $remplace, $query[$k]);
2564 if ($ai) {
2565 $query[$k] = preg_replace(array_keys($remplace_autocinc), $remplace_autocinc, $query[$k]);
2566 } else {
2567 $query[$k] = preg_replace(array_keys($remplace_nonautocinc), $remplace_nonautocinc, $query[$k]);
2568 $query[$k] = _sqlite_collate_ci($query[$k]);
2569 }
2570 }
2571 }
2572
2573 return $query;
2574 }
2575
2576 /**
2577 * Definir la collation d'un champ en fonction de si une collation est deja explicite
2578 * et du par defaut que l'on veut NOCASE
2579 *
2580 * @param string $champ
2581 * @return string
2582 */
2583 function _sqlite_collate_ci($champ) {
2584 if (stripos($champ, "COLLATE") !== false) {
2585 return $champ;
2586 }
2587 if (stripos($champ, "BINARY") !== false) {
2588 return str_ireplace("BINARY", "COLLATE BINARY", $champ);
2589 }
2590 if (preg_match(",^(char|varchar|(long|small|medium|tiny)?text),i", $champ)) {
2591 return $champ . " COLLATE NOCASE";
2592 }
2593
2594 return $champ;
2595 }
2596
2597
2598 /**
2599 * Creer la requete pour la creation d'une table
2600 * retourne la requete pour utilisation par sql_create() et sql_alter()
2601 *
2602 * @param $nom
2603 * @param $champs
2604 * @param $cles
2605 * @param bool $autoinc
2606 * @param bool $temporary
2607 * @param bool $_ifnotexists
2608 * @param string $serveur
2609 * @param bool $requeter
2610 * @return bool|string
2611 */
2612 function _sqlite_requete_create(
2613 $nom,
2614 $champs,
2615 $cles,
2616 $autoinc = false,
2617 $temporary = false,
2618 $_ifnotexists = true,
2619 $serveur = '',
2620 $requeter = true
2621 ) {
2622 $query = $keys = $s = $p = '';
2623
2624 // certains plugins declarent les tables (permet leur inclusion dans le dump)
2625 // sans les renseigner (laisse le compilo recuperer la description)
2626 if (!is_array($champs) || !is_array($cles)) {
2627 return;
2628 }
2629
2630 // sqlite ne gere pas KEY tout court dans une requete CREATE TABLE
2631 // il faut passer par des create index
2632 // Il gere par contre primary key !
2633 // Soit la PK est definie dans les cles, soit dans un champs
2634 // soit faussement dans les 2 (et dans ce cas, il faut l’enlever à un des 2 endroits !)
2635 $pk = "PRIMARY KEY";
2636 // le champ de cle primaire
2637 $c = !empty($cles[$pk]) ? $cles[$pk] : '';
2638
2639 foreach ($champs as $k => $v) {
2640 if (false !== stripos($v, $pk)) {
2641 $c = $k;
2642 // on n'en a plus besoin dans field, vu que defini dans key
2643 $champs[$k] = preg_replace("/$pk/is", '', $champs[$k]);
2644 break;
2645 }
2646 }
2647
2648 if ($c) {
2649 $keys = "\n\t\t$pk ($c)";
2650 }
2651 // Pas de DEFAULT 0 sur les cles primaires en auto-increment
2652 if (isset($champs[$c])
2653 and stripos($champs[$c], "default 0") !== false
2654 ) {
2655 $champs[$c] = trim(str_ireplace("default 0", "", $champs[$c]));
2656 }
2657
2658 $champs = _sqlite_remplacements_definitions_table($champs, $autoinc ? $c : false);
2659 foreach ($champs as $k => $v) {
2660 $query .= "$s\n\t\t$k $v";
2661 $s = ",";
2662 }
2663
2664 $ifnotexists = "";
2665 if ($_ifnotexists) {
2666
2667 $version = spip_sqlite_fetch(spip_sqlite_query("select sqlite_version() AS sqlite_version", $serveur), '',
2668 $serveur);
2669 if (!function_exists('spip_version_compare')) {
2670 include_spip('plugins/installer');
2671 }
2672
2673 if ($version and spip_version_compare($version['sqlite_version'], '3.3.0', '>=')) {
2674 $ifnotexists = ' IF NOT EXISTS';
2675 } else {
2676 /* simuler le IF EXISTS - version 2 et sqlite < 3.3a */
2677 $a = spip_sqlite_showtable($nom, $serveur);
2678 if (isset($a['key']['KEY ' . $nom])) {
2679 return true;
2680 }
2681 }
2682
2683 }
2684
2685 $temporary = $temporary ? ' TEMPORARY' : '';
2686 $q = "CREATE$temporary TABLE$ifnotexists $nom ($query" . ($keys ? ",$keys" : '') . ")\n";
2687
2688 return $q;
2689 }
2690
2691
2692 /**
2693 * Retrouver les champs 'timestamp'
2694 * pour les ajouter aux 'insert' ou 'replace'
2695 * afin de simuler le fonctionnement de mysql
2696 *
2697 * stocke le resultat pour ne pas faire
2698 * de requetes showtable intempestives
2699 *
2700 * @param $table
2701 * @param $couples
2702 * @param string $desc
2703 * @param string $serveur
2704 * @return
2705 */
2706 function _sqlite_ajouter_champs_timestamp($table, $couples, $desc = '', $serveur = '') {
2707 static $tables = array();
2708
2709 if (!isset($tables[$table])) {
2710
2711 if (!$desc) {
2712 $trouver_table = charger_fonction('trouver_table', 'base');
2713 $desc = $trouver_table($table, $serveur);
2714 // si pas de description, on ne fait rien, ou on die() ?
2715 if (!$desc) {
2716 return $couples;
2717 }
2718 }
2719
2720 // recherche des champs avec simplement 'TIMESTAMP'
2721 // cependant, il faudra peut etre etendre
2722 // avec la gestion de DEFAULT et ON UPDATE
2723 // mais ceux-ci ne sont pas utilises dans le core
2724 $tables[$table] = array();
2725
2726 foreach ($desc['field'] as $k => $v) {
2727 if (strpos(strtolower(ltrim($v)), 'timestamp') === 0) {
2728 $tables[$table][$k] = "datetime('now')";
2729 }
2730 }
2731 }
2732
2733 // ajout des champs type 'timestamp' absents
2734 return array_merge($tables[$table], $couples);
2735 }
2736
2737
2738 /**
2739 * Renvoyer la liste des versions sqlite disponibles
2740 * sur le serveur
2741 *
2742 * @return array|bool
2743 */
2744 function spip_versions_sqlite() {
2745 return _sqlite_charger_version();
2746 }
2747
2748 /**
2749 * Gère l'envoi et la réception de requêtes à SQLite, qui peuvent être
2750 * encadrées de transactions.
2751 **/
2752 class spip_sqlite {
2753 /** @var sqlite_requeteur[] Liste des instances de requêteurs créés */
2754 public static $requeteurs = array();
2755 /** @var bool[] Pour chaque connexion, flag pour savoir si une transaction est en cours */
2756 public static $transaction_en_cours = array();
2757
2758
2759 /**
2760 * Retourne une unique instance du requêteur
2761 *
2762 * Retourne une instance unique du requêteur pour une connexion SQLite
2763 * donnée
2764 *
2765 * @param string $serveur
2766 * Nom du connecteur
2767 * @return sqlite_requeteur
2768 * Instance unique du requêteur
2769 **/
2770 public static function requeteur($serveur) {
2771 if (!isset(spip_sqlite::$requeteurs[$serveur])) {
2772 spip_sqlite::$requeteurs[$serveur] = new sqlite_requeteur($serveur);
2773 }
2774
2775 return spip_sqlite::$requeteurs[$serveur];
2776 }
2777
2778 /**
2779 * Prépare le texte d'une requête avant son exécution
2780 *
2781 * Adapte la requête au format plus ou moins MySQL par un format
2782 * compris de SQLite.
2783 *
2784 * Change les préfixes de tables SPIP par ceux véritables
2785 *
2786 * @param string $query Requête à préparer
2787 * @param string $serveur Nom de la connexion
2788 * @return string Requête préparée
2789 */
2790 public static function traduire_requete($query, $serveur) {
2791 $requeteur = spip_sqlite::requeteur($serveur);
2792 $traducteur = new sqlite_traducteur($query, $requeteur->prefixe, $requeteur->sqlite_version);
2793
2794 return $traducteur->traduire_requete();
2795 }
2796
2797 /**
2798 * Démarre une transaction
2799 *
2800 * @param string $serveur Nom de la connexion
2801 **/
2802 public static function demarrer_transaction($serveur) {
2803 spip_sqlite::executer_requete("BEGIN TRANSACTION", $serveur);
2804 spip_sqlite::$transaction_en_cours[$serveur] = true;
2805 }
2806
2807 /**
2808 * Exécute la requête donnée
2809 *
2810 * @param string $query Requête
2811 * @param string $serveur Nom de la connexion
2812 * @param null|bool $tracer Demander des statistiques (temps) ?
2813 **/
2814 public static function executer_requete($query, $serveur, $tracer = null) {
2815 $requeteur = spip_sqlite::requeteur($serveur);
2816
2817 return $requeteur->executer_requete($query, $tracer);
2818 }
2819
2820 /**
2821 * Obtient l'identifiant de la dernière ligne insérée ou modifiée
2822 *
2823 * @param string $serveur Nom de la connexion
2824 * return int Identifiant
2825 **/
2826 public static function last_insert_id($serveur) {
2827 $requeteur = spip_sqlite::requeteur($serveur);
2828
2829 return $requeteur->last_insert_id($serveur);
2830 }
2831
2832 /**
2833 * Annule une transaction
2834 *
2835 * @param string $serveur Nom de la connexion
2836 **/
2837 public static function annuler_transaction($serveur) {
2838 spip_sqlite::executer_requete("ROLLBACK", $serveur);
2839 spip_sqlite::$transaction_en_cours[$serveur] = false;
2840 }
2841
2842 /**
2843 * Termine une transaction
2844 *
2845 * @param string $serveur Nom de la connexion
2846 **/
2847 public static function finir_transaction($serveur) {
2848 // si pas de transaction en cours, ne rien faire et le dire
2849 if (!isset(spip_sqlite::$transaction_en_cours[$serveur])
2850 or spip_sqlite::$transaction_en_cours[$serveur] == false
2851 ) {
2852 return false;
2853 }
2854 // sinon fermer la transaction et retourner true
2855 spip_sqlite::executer_requete("COMMIT", $serveur);
2856 spip_sqlite::$transaction_en_cours[$serveur] = false;
2857
2858 return true;
2859 }
2860 }
2861
2862 /*
2863 * Classe pour partager les lancements de requête
2864 *
2865 * Instanciée une fois par `$serveur` :
2866 *
2867 * - peut corriger la syntaxe des requêtes pour la conformité à SQLite
2868 * - peut tracer les requêtes
2869 */
2870
2871 class sqlite_requeteur {
2872 /** @var string Texte de la requête */
2873 public $query = ''; // la requete
2874 /** @var string Nom de la connexion */
2875 public $serveur = '';
2876 /** @var Ressource Identifiant de la connexion SQLite */
2877 public $link = '';
2878 /** @var string Prefixe des tables SPIP */
2879 public $prefixe = '';
2880 /** @var string Nom de la base de donnée */
2881 public $db = '';
2882 /** @var bool Doit-on tracer les requetes (var_profile) ? */
2883 public $tracer = false; // doit-on tracer les requetes (var_profile)
2884
2885 /** @var string Version de SQLite (2 ou 3) */
2886 public $sqlite_version = '';
2887
2888 /**
2889 * Constructeur
2890 *
2891 * @param string $serveur
2892 * @return bool
2893 */
2894 public function __construct($serveur = '') {
2895 _sqlite_init();
2896 $this->serveur = strtolower($serveur);
2897
2898 if (!($this->link = _sqlite_link($this->serveur)) && (!defined('_ECRIRE_INSTALL') || !_ECRIRE_INSTALL)) {
2899 spip_log("Aucune connexion sqlite (link)", 'sqlite.' . _LOG_ERREUR);
2900
2901 return false;
2902 }
2903
2904 $this->sqlite_version = _sqlite_is_version('', $this->link);
2905
2906 $this->prefixe = $GLOBALS['connexions'][$this->serveur ? $this->serveur : 0]['prefixe'];
2907 $this->db = $GLOBALS['connexions'][$this->serveur ? $this->serveur : 0]['db'];
2908
2909 // tracage des requetes ?
2910 $this->tracer = (isset($_GET['var_profile']) && $_GET['var_profile']);
2911 }
2912
2913 /**
2914 * Lancer la requête transmise et faire le tracage si demandé
2915 *
2916 * @param string $query
2917 * Requête à exécuter
2918 * @param bool|null $tracer
2919 * true pour tracer la requête
2920 * @return bool|SQLiteResult
2921 */
2922 public function executer_requete($query, $tracer = null) {
2923 if (is_null($tracer)) {
2924 $tracer = $this->tracer;
2925 }
2926 $err = "";
2927 $t = 0;
2928 if ($tracer) {
2929 include_spip('public/tracer');
2930 $t = trace_query_start();
2931 }
2932
2933 # spip_log("requete: $this->serveur >> $query",'sqlite.'._LOG_DEBUG); // boum ? pourquoi ?
2934 if ($this->link) {
2935 // memoriser la derniere erreur PHP vue
2936 $e = (function_exists('error_get_last') ? error_get_last() : "");
2937 // sauver la derniere requete
2938 $GLOBALS['connexions'][$this->serveur ? $this->serveur : 0]['last'] = $query;
2939 $GLOBALS['connexions'][$this->serveur ? $this->serveur : 0]['total_requetes']++;
2940
2941 if ($this->sqlite_version == 3) {
2942 $r = $this->link->query($query);
2943 // sauvegarde de la requete (elle y est deja dans $r->queryString)
2944 # $r->spipQueryString = $query;
2945
2946 // comptage : oblige de compter le nombre d'entrees retournees
2947 // par une requete SELECT
2948 // aucune autre solution ne donne le nombre attendu :( !
2949 // particulierement s'il y a des LIMIT dans la requete.
2950 if (strtoupper(substr(ltrim($query), 0, 6)) == 'SELECT') {
2951 if ($r) {
2952 // noter le link et la query pour faire le comptage *si* on en a besoin
2953 $r->spipSqliteRowCount = array($this->link, $query);
2954 } elseif ($r instanceof PDOStatement) {
2955 $r->spipSqliteRowCount = 0;
2956 }
2957 }
2958 } else {
2959 $r = sqlite_query($this->link, $query);
2960 }
2961
2962 // loger les warnings/erreurs eventuels de sqlite remontant dans PHP
2963 if ($err = (function_exists('error_get_last') ? error_get_last() : "") and $err != $e) {
2964 $err = strip_tags($err['message']) . " in " . $err['file'] . " line " . $err['line'];
2965 spip_log("$err - " . $query, 'sqlite.' . _LOG_ERREUR);
2966 } else {
2967 $err = "";
2968 }
2969
2970 } else {
2971 $r = false;
2972 }
2973
2974 if (spip_sqlite_errno($this->serveur)) {
2975 $err .= spip_sqlite_error($query, $this->serveur);
2976 }
2977
2978 return $t ? trace_query_end($query, $t, $r, $err, $this->serveur) : $r;
2979 }
2980
2981 /**
2982 * Obtient l'identifiant de la dernière ligne insérée ou modifiée
2983 *
2984 * @return int
2985 **/
2986 public function last_insert_id() {
2987 if ($this->sqlite_version == 3) {
2988 return $this->link->lastInsertId();
2989 } else {
2990 return sqlite_last_insert_rowid($this->link);
2991 }
2992 }
2993 }
2994
2995
2996 /**
2997 * Cette classe est presente essentiellement pour un preg_replace_callback
2998 * avec des parametres dans la fonction appelee que l'on souhaite incrementer
2999 * (fonction pour proteger les textes)
3000 */
3001 class sqlite_traducteur {
3002 /** @var string $query Texte de la requête */
3003 public $query = '';
3004 /** @var string $prefixe Préfixe des tables */
3005 public $prefixe = '';
3006 /** @var string $sqlite_version Version de sqlite (2 ou 3) */
3007 public $sqlite_version = '';
3008
3009 /** Pour les corrections à effectuer sur les requêtes : array(code=>'texte') trouvé
3010 *
3011 * @var array
3012 */
3013 public $textes = array();
3014
3015 /**
3016 * Constructeur
3017 *
3018 * @param string $query Requête à préparer
3019 * @param string $prefixe Prefixe des tables à utiliser
3020 * @param string $sqlite_version Version SQLite (2 ou 3)
3021 */
3022 public function __construct($query, $prefixe, $sqlite_version) {
3023 $this->query = $query;
3024 $this->prefixe = $prefixe;
3025 $this->sqlite_version = $sqlite_version;
3026 }
3027
3028 /**
3029 * Transformer la requete pour SQLite
3030 *
3031 * Enlève les textes, transforme la requête pour quelle soit
3032 * bien interprétée par SQLite, puis remet les textes
3033 * la fonction affecte `$this->query`
3034 */
3035 public function traduire_requete() {
3036 //
3037 // 1) Protection des textes en les remplacant par des codes
3038 //
3039 // enlever les 'textes' et initialiser avec
3040 list($this->query, $textes) = query_echappe_textes($this->query);
3041
3042 //
3043 // 2) Corrections de la requete
3044 //
3045 // Correction Create Database
3046 // Create Database -> requete ignoree
3047 if (strpos($this->query, 'CREATE DATABASE') === 0) {
3048 spip_log("Sqlite : requete non executee -> $this->query", 'sqlite.' . _LOG_AVERTISSEMENT);
3049 $this->query = "SELECT 1";
3050 }
3051
3052 // Correction Insert Ignore
3053 // INSERT IGNORE -> insert (tout court et pas 'insert or replace')
3054 if (strpos($this->query, 'INSERT IGNORE') === 0) {
3055 spip_log("Sqlite : requete transformee -> $this->query", 'sqlite.' . _LOG_DEBUG);
3056 $this->query = 'INSERT ' . substr($this->query, '13');
3057 }
3058
3059 // Correction des dates avec INTERVAL
3060 // utiliser sql_date_proche() de preference
3061 if (strpos($this->query, 'INTERVAL') !== false) {
3062 $this->query = preg_replace_callback("/DATE_(ADD|SUB)(.*)INTERVAL\s+(\d+)\s+([a-zA-Z]+)\)/U",
3063 array(&$this, '_remplacerDateParTime'),
3064 $this->query);
3065 }
3066
3067 if (strpos($this->query, 'LEFT(') !== false) {
3068 $this->query = str_replace('LEFT(', '_LEFT(', $this->query);
3069 }
3070
3071 if (strpos($this->query, 'TIMESTAMPDIFF(') !== false) {
3072 $this->query = preg_replace('/TIMESTAMPDIFF\(\s*([^,]*)\s*,/Uims', "TIMESTAMPDIFF('\\1',", $this->query);
3073 }
3074
3075
3076 // Correction Using
3077 // USING (non reconnu en sqlite2)
3078 // problematique car la jointure ne se fait pas du coup.
3079 if (($this->sqlite_version == 2) && (strpos($this->query, "USING") !== false)) {
3080 spip_log("'USING (champ)' n'est pas reconnu en SQLite 2. Utilisez 'ON table1.champ = table2.champ'",
3081 'sqlite.' . _LOG_ERREUR);
3082 $this->query = preg_replace('/USING\s*\([^\)]*\)/', '', $this->query);
3083 }
3084
3085 // Correction Field
3086 // remplace FIELD(table,i,j,k...) par CASE WHEN table=i THEN n ... ELSE 0 END
3087 if (strpos($this->query, 'FIELD') !== false) {
3088 $this->query = preg_replace_callback('/FIELD\s*\(([^\)]*)\)/',
3089 array(&$this, '_remplacerFieldParCase'),
3090 $this->query);
3091 }
3092
3093 // Correction des noms de tables FROM
3094 // mettre les bons noms de table dans from, update, insert, replace...
3095 if (preg_match('/\s(SET|VALUES|WHERE|DATABASE)\s/iS', $this->query, $regs)) {
3096 $suite = strstr($this->query, $regs[0]);
3097 $this->query = substr($this->query, 0, -strlen($suite));
3098 } else {
3099 $suite = '';
3100 }
3101 $pref = ($this->prefixe) ? $this->prefixe . "_" : "";
3102 $this->query = preg_replace('/([,\s])spip_/S', '\1' . $pref, $this->query) . $suite;
3103
3104 // Correction zero AS x
3105 // pg n'aime pas 0+x AS alias, sqlite, dans le meme style,
3106 // n'apprecie pas du tout SELECT 0 as x ... ORDER BY x
3107 // il dit que x ne doit pas être un integer dans le order by !
3108 // on remplace du coup x par vide() dans ce cas uniquement
3109 //
3110 // rien que pour public/vertebrer.php ?
3111 if ((strpos($this->query, "0 AS") !== false)) {
3112 // on ne remplace que dans ORDER BY ou GROUP BY
3113 if (preg_match('/\s(ORDER|GROUP) BY\s/i', $this->query, $regs)) {
3114 $suite = strstr($this->query, $regs[0]);
3115 $this->query = substr($this->query, 0, -strlen($suite));
3116
3117 // on cherche les noms des x dans 0 AS x
3118 // on remplace dans $suite le nom par vide()
3119 preg_match_all('/\b0 AS\s*([^\s,]+)/', $this->query, $matches, PREG_PATTERN_ORDER);
3120 foreach ($matches[1] as $m) {
3121 $suite = str_replace($m, 'VIDE()', $suite);
3122 }
3123 $this->query .= $suite;
3124 }
3125 }
3126
3127 // Correction possible des divisions entieres
3128 // Le standard SQL (lequel? ou?) semble indiquer que
3129 // a/b=c doit donner c entier si a et b sont entiers 4/3=1.
3130 // C'est ce que retournent effectivement SQL Server et SQLite
3131 // Ce n'est pas ce qu'applique MySQL qui retourne un reel : 4/3=1.333...
3132 //
3133 // On peut forcer la conversion en multipliant par 1.0 avant la division
3134 // /!\ SQLite 3.5.9 Debian/Ubuntu est victime d'un bug en plus !
3135 // cf. https://bugs.launchpad.net/ubuntu/+source/sqlite3/+bug/254228
3136 // http://www.sqlite.org/cvstrac/tktview?tn=3202
3137 // (4*1.0/3) n'est pas rendu dans ce cas !
3138 # $this->query = str_replace('/','* 1.00 / ',$this->query);
3139
3140
3141 // Correction critere REGEXP, non reconnu en sqlite2
3142 if (($this->sqlite_version == 2) && (strpos($this->query, 'REGEXP') !== false)) {
3143 $this->query = preg_replace('/([^\s\(]*)(\s*)REGEXP(\s*)([^\s\)]*)/', 'REGEXP($4, $1)', $this->query);
3144 }
3145
3146 //
3147 // 3) Remise en place des textes d'origine
3148 //
3149 // Correction Antiquotes et echappements
3150 // ` => rien
3151 if (strpos($this->query, '`') !== false) {
3152 $this->query = str_replace('`', '', $this->query);
3153 }
3154
3155 $this->query = query_reinjecte_textes($this->query, $textes);
3156
3157 return $this->query;
3158 }
3159
3160
3161 /**
3162 * Callback pour remplacer `DATE_` / `INTERVAL`
3163 * par `DATE ... strtotime`
3164 *
3165 * @param array $matches Captures
3166 * @return string Texte de date compris par SQLite
3167 */
3168 public function _remplacerDateParTime($matches) {
3169 $op = strtoupper($matches[1] == 'ADD') ? '+' : '-';
3170
3171 return "datetime$matches[2] '$op$matches[3] $matches[4]')";
3172 }
3173
3174 /**
3175 * Callback pour remplacer `FIELD(table,i,j,k...)`
3176 * par `CASE WHEN table=i THEN n ... ELSE 0 END`
3177 *
3178 * @param array $matches Captures
3179 * @return string Texte de liste ordonnée compris par SQLite
3180 */
3181 public function _remplacerFieldParCase($matches) {
3182 $fields = substr($matches[0], 6, -1); // ne recuperer que l'interieur X de field(X)
3183 $t = explode(',', $fields);
3184 $index = array_shift($t);
3185
3186 $res = '';
3187 $n = 0;
3188 foreach ($t as $v) {
3189 $n++;
3190 $res .= "\nWHEN $index=$v THEN $n";
3191 }
3192
3193 return "CASE $res ELSE 0 END ";
3194 }
3195
3196 }