42cf24b99e8f46f20cea239cc7181b4ca0aab4de
[lhc/web/www.git] / www / ecrire / req / pg.exp.php
1 <?php
2
3 /* *************************************************************************\
4 * SPIP, Systeme de publication pour l'internet *
5 * *
6 * Copyright (c) 2001-2017 *
7 * Arnaud Martin, Antoine Pitrou, Philippe Riviere, Emmanuel Saint-James *
8 * *
9 * Ce programme est un logiciel libre distribue sous licence GNU/GPL. *
10 * Pour plus de details voir le fichier COPYING.txt ou l'aide en ligne. *
11 \***************************************************************************/
12
13 /**
14 * Ce fichier contient les fonctions gerant
15 * les instructions SQL pour PostgreSQL
16 *
17 * @package SPIP\Core\SQL\PostgreSQL
18 */
19
20 if (!defined('_ECRIRE_INC_VERSION')) {
21 return;
22 }
23
24 define('_DEFAULT_DB', 'spip');
25
26 // Se connecte et retourne le nom de la fonction a connexion persistante
27 // A la premiere connexion de l'installation (BD pas precisee)
28 // si on ne peut se connecter sans la preciser
29 // on reessaye avec le login comme nom de BD
30 // et si ca marche toujours pas, avec "spip" (constante ci-dessus)
31 // si ca ne marche toujours pas, echec.
32
33 // http://code.spip.net/@req_pg_dist
34 function req_pg_dist($addr, $port, $login, $pass, $db = '', $prefixe = '') {
35 static $last_connect = array();
36 if (!charger_php_extension('pgsql')) {
37 return false;
38 }
39
40 // si provient de selectdb
41 if (empty($addr) && empty($port) && empty($login) && empty($pass)) {
42 foreach (array('addr', 'port', 'login', 'pass', 'prefixe') as $a) {
43 $$a = $last_connect[$a];
44 }
45 }
46 @list($host, $p) = explode(';', $addr);
47 if ($p > 0) {
48 $port = " port=$p";
49 } else {
50 $port = '';
51 }
52 $erreurs = array();
53 if ($db) {
54 @$link = pg_connect("host=$host$port dbname=$db user=$login password='$pass'", PGSQL_CONNECT_FORCE_NEW);
55 } elseif (!@$link = pg_connect("host=$host$port user=$login password='$pass'", PGSQL_CONNECT_FORCE_NEW)) {
56 $erreurs[] = pg_last_error();
57 if (@$link = pg_connect("host=$host$port dbname=$login user=$login password='$pass'", PGSQL_CONNECT_FORCE_NEW)) {
58 $db = $login;
59 } else {
60 $erreurs[] = pg_last_error();
61 $db = _DEFAULT_DB;
62 $link = pg_connect("host=$host$port dbname=$db user=$login password='$pass'", PGSQL_CONNECT_FORCE_NEW);
63 }
64 }
65 if (!$link) {
66 $erreurs[] = pg_last_error();
67 foreach ($erreurs as $e) {
68 spip_log('Echec pg_connect. Erreur : ' . $e, 'pg.' . _LOG_HS);
69 }
70
71 return false;
72 }
73
74 if ($link) {
75 $last_connect = array(
76 'addr' => $addr,
77 'port' => $port,
78 'login' => $login,
79 'pass' => $pass,
80 'db' => $db,
81 'prefixe' => $prefixe,
82 );
83 }
84
85 spip_log("Connexion vers $host, base $db, prefixe $prefixe " . ($link ? 'operationnelle' : 'impossible'),
86 'pg.' . _LOG_DEBUG);
87
88 return !$link ? false : array(
89 'db' => $db,
90 'prefixe' => $prefixe ? $prefixe : $db,
91 'link' => $link,
92 );
93 }
94
95 $GLOBALS['spip_pg_functions_1'] = array(
96 'alter' => 'spip_pg_alter',
97 'count' => 'spip_pg_count',
98 'countsel' => 'spip_pg_countsel',
99 'create' => 'spip_pg_create',
100 'create_base' => 'spip_pg_create_base',
101 'create_view' => 'spip_pg_create_view',
102 'date_proche' => 'spip_pg_date_proche',
103 'delete' => 'spip_pg_delete',
104 'drop_table' => 'spip_pg_drop_table',
105 'drop_view' => 'spip_pg_drop_view',
106 'errno' => 'spip_pg_errno',
107 'error' => 'spip_pg_error',
108 'explain' => 'spip_pg_explain',
109 'fetch' => 'spip_pg_fetch',
110 'seek' => 'spip_pg_seek',
111 'free' => 'spip_pg_free',
112 'hex' => 'spip_pg_hex',
113 'in' => 'spip_pg_in',
114 'insert' => 'spip_pg_insert',
115 'insertq' => 'spip_pg_insertq',
116 'insertq_multi' => 'spip_pg_insertq_multi',
117 'listdbs' => 'spip_pg_listdbs',
118 'multi' => 'spip_pg_multi',
119 'optimize' => 'spip_pg_optimize',
120 'query' => 'spip_pg_query',
121 'quote' => 'spip_pg_quote',
122 'replace' => 'spip_pg_replace',
123 'replace_multi' => 'spip_pg_replace_multi',
124 'select' => 'spip_pg_select',
125 'selectdb' => 'spip_pg_selectdb',
126 'set_connect_charset' => 'spip_pg_set_connect_charset',
127 'showbase' => 'spip_pg_showbase',
128 'showtable' => 'spip_pg_showtable',
129 'update' => 'spip_pg_update',
130 'updateq' => 'spip_pg_updateq',
131 );
132
133 // Par ou ca passe une fois les traductions faites
134 // http://code.spip.net/@spip_pg_trace_query
135 function spip_pg_trace_query($query, $serveur = '') {
136 $connexion = &$GLOBALS['connexions'][$serveur ? strtolower($serveur) : 0];
137 $prefixe = $connexion['prefixe'];
138 $link = $connexion['link'];
139 $db = $connexion['db'];
140
141 if (isset($_GET['var_profile'])) {
142 include_spip('public/tracer');
143 $t = trace_query_start();
144 $e = '';
145 } else {
146 $t = 0;
147 }
148
149 $connexion['last'] = $query;
150 $r = spip_pg_query_simple($link, $query);
151
152 // Log de l'erreur eventuelle
153 if ($e = spip_pg_errno($serveur)) {
154 $e .= spip_pg_error($query, $serveur);
155 } // et du fautif
156 return $t ? trace_query_end($query, $t, $r, $e, $serveur) : $r;
157 }
158
159 // Fonction de requete generale quand on est sur que c'est SQL standard.
160 // Elle change juste le noms des tables ($table_prefix) dans le FROM etc
161
162 // http://code.spip.net/@spip_pg_query
163 function spip_pg_query($query, $serveur = '', $requeter = true) {
164 $connexion = &$GLOBALS['connexions'][$serveur ? strtolower($serveur) : 0];
165 $prefixe = $connexion['prefixe'];
166 $link = $connexion['link'];
167 $db = $connexion['db'];
168
169 if (preg_match('/\s(SET|VALUES|WHERE|DATABASE)\s/i', $query, $regs)) {
170 $suite = strstr($query, $regs[0]);
171 $query = substr($query, 0, -strlen($suite));
172 } else {
173 $suite = '';
174 }
175 $query = preg_replace('/([,\s])spip_/', '\1' . $prefixe . '_', $query) . $suite;
176
177 // renvoyer la requete inerte si demandee
178 if (!$requeter) {
179 return $query;
180 }
181
182 return spip_pg_trace_query($query, $serveur);
183 }
184
185 function spip_pg_query_simple($link, $query) {
186 #spip_log(var_export($query,true), 'pg.'._LOG_DEBUG);
187 return pg_query($link, $query);
188 }
189
190 /*
191 * Retrouver les champs 'timestamp'
192 * pour les ajouter aux 'insert' ou 'replace'
193 * afin de simuler le fonctionnement de mysql
194 *
195 * stocke le resultat pour ne pas faire
196 * de requetes showtable intempestives
197 */
198 function spip_pg_ajouter_champs_timestamp($table, $couples, $desc = '', $serveur = '') {
199 static $tables = array();
200
201 if (!isset($tables[$table])) {
202
203 if (!$desc) {
204 $trouver_table = charger_fonction('trouver_table', 'base');
205 $desc = $trouver_table($table, $serveur);
206 // si pas de description, on ne fait rien, ou on die() ?
207 if (!$desc) {
208 return $couples;
209 }
210 }
211
212 // recherche des champs avec simplement 'TIMESTAMP'
213 // cependant, il faudra peut etre etendre
214 // avec la gestion de DEFAULT et ON UPDATE
215 // mais ceux-ci ne sont pas utilises dans le core
216 $tables[$table] = array();
217 foreach ($desc['field'] as $k => $v) {
218 $v = strtolower(ltrim($v));
219 // ne pas ajouter de timestamp now() si un default est specifie
220 if (strpos($v, 'timestamp') === 0 and strpos($v, 'default') === false) {
221 $tables[$table][] = $k;
222 }
223 }
224 }
225
226 // ajout des champs type 'timestamp' absents
227 foreach ($tables[$table] as $maj) {
228 if (!array_key_exists($maj, $couples)) {
229 $couples[$maj] = "NOW()";
230 }
231 }
232
233 return $couples;
234 }
235
236
237 // Alter en PG ne traite pas les index
238 // http://code.spip.net/@spip_pg_alter
239 function spip_pg_alter($query, $serveur = '', $requeter = true) {
240 // il faudrait une regexp pour eviter de spliter ADD PRIMARY KEY (colA, colB)
241 // tout en cassant en deux alter distincts "ADD PRIMARY KEY (colA, colB), ADD INDEX (chose)"...
242 // ou revoir l'api de sql_alter en creant un
243 // sql_alter_table($table,array($actions));
244 if (!preg_match("/\s*((\s*IGNORE)?\s*TABLE\s*([^\s]*))\s*(.*)?/is", $query, $regs)) {
245 spip_log("$query mal comprise", 'pg.' . _LOG_ERREUR);
246
247 return false;
248 }
249 $debut = $regs[1];
250 $table = $regs[3];
251 $suite = $regs[4];
252 $todo = explode(',', $suite);
253 // on remet les morceaux dechires ensembles... que c'est laid !
254 $todo2 = array();
255 $i = 0;
256 $ouverte = false;
257 while ($do = array_shift($todo)) {
258 $todo2[$i] = isset($todo2[$i]) ? $todo2[$i] . "," . $do : $do;
259 $o = (false !== strpos($do, "("));
260 $f = (false !== strpos($do, ")"));
261 if ($o and !$f) {
262 $ouverte = true;
263 } elseif ($f) {
264 $ouverte = false;
265 }
266 if (!$ouverte) {
267 $i++;
268 }
269 }
270 $todo = $todo2;
271 $query = $debut . ' ' . array_shift($todo);
272
273 if (!preg_match('/^\s*(IGNORE\s*)?TABLE\s+(\w+)\s+(ADD|DROP|CHANGE|MODIFY|RENAME)\s*(.*)$/is', $query, $r)) {
274 spip_log("$query incompris", 'pg.' . _LOG_ERREUR);
275 } else {
276 if ($r[1]) {
277 spip_log("j'ignore IGNORE dans $query", 'pg.' . _LOG_AVERTISSEMENT);
278 }
279 $f = 'spip_pg_alter_' . strtolower($r[3]);
280 if (function_exists($f)) {
281 $f($r[2], $r[4], $serveur, $requeter);
282 } else {
283 spip_log("$query non prevu", 'pg.' . _LOG_ERREUR);
284 }
285 }
286 // Alter a plusieurs args. Faudrait optimiser.
287 if ($todo) {
288 spip_pg_alter("TABLE $table " . join(',', $todo));
289 }
290
291 }
292
293 // http://code.spip.net/@spip_pg_alter_change
294 function spip_pg_alter_change($table, $arg, $serveur = '', $requeter = true) {
295 if (!preg_match('/^`?(\w+)`?\s+`?(\w+)`?\s+(.*?)\s*(DEFAULT .*?)?(NOT\s+NULL)?\s*(DEFAULT .*?)?$/i', $arg, $r)) {
296 spip_log("alter change: $arg incompris", 'pg.' . _LOG_ERREUR);
297 } else {
298 list(, $old, $new, $type, $default, $null, $def2) = $r;
299 $actions = array("ALTER $old TYPE " . mysql2pg_type($type));
300 if ($null) {
301 $actions[] = "ALTER $old SET NOT NULL";
302 } else {
303 $actions[] = "ALTER $old DROP NOT NULL";
304 }
305
306 if ($d = ($default ? $default : $def2)) {
307 $actions[] = "ALTER $old SET $d";
308 } else {
309 $actions[] = "ALTER $old DROP DEFAULT";
310 }
311
312 spip_pg_query("ALTER TABLE $table " . join(', ', $actions));
313
314 if ($old != $new) {
315 spip_pg_query("ALTER TABLE $table RENAME $old TO $new", $serveur);
316 }
317 }
318 }
319
320 // http://code.spip.net/@spip_pg_alter_add
321 function spip_pg_alter_add($table, $arg, $serveur = '', $requeter = true) {
322 if (!preg_match('/^(COLUMN|INDEX|KEY|PRIMARY\s+KEY|)\s*(.*)$/', $arg, $r)) {
323 spip_log("alter add $arg incompris", 'pg.' . _LOG_ERREUR);
324
325 return null;
326 }
327 if (!$r[1] or $r[1] == 'COLUMN') {
328 preg_match('/`?(\w+)`?(.*)/', $r[2], $m);
329 if (preg_match('/^(.*)(BEFORE|AFTER|FIRST)(.*)$/is', $m[2], $n)) {
330 $m[2] = $n[1];
331 }
332
333 return spip_pg_query("ALTER TABLE $table ADD " . $m[1] . ' ' . mysql2pg_type($m[2]), $serveur, $requeter);
334 } elseif ($r[1][0] == 'P') {
335 // la primary peut etre sur plusieurs champs
336 $r[2] = trim(str_replace('`', '', $r[2]));
337 $m = ($r[2][0] == '(') ? substr($r[2], 1, -1) : $r[2];
338
339 return spip_pg_query("ALTER TABLE $table ADD CONSTRAINT $table" . '_pkey PRIMARY KEY (' . $m . ')', $serveur,
340 $requeter);
341 } else {
342 preg_match('/([^\s,]*)\s*(.*)?/', $r[2], $m);
343 // peut etre "(colonne)" ou "nom_index (colonnes)"
344 // bug potentiel si qqn met "(colonne, colonne)"
345 //
346 // nom_index (colonnes)
347 if ($m[2]) {
348 $colonnes = substr($m[2], 1, -1);
349 $nom_index = $m[1];
350 } else {
351 // (colonne)
352 if ($m[1][0] == "(") {
353 $colonnes = substr($m[1], 1, -1);
354 if (false !== strpos(",", $colonnes)) {
355 spip_log(_LOG_GRAVITE_ERREUR, "PG : Erreur, impossible de creer un index sur plusieurs colonnes"
356 . " sans qu'il ait de nom ($table, ($colonnes))", 'pg');
357 } else {
358 $nom_index = $colonnes;
359 }
360 } // nom_index
361 else {
362 $nom_index = $colonnes = $m[1];
363 }
364 }
365
366 return spip_pg_create_index($nom_index, $table, $colonnes, $serveur, $requeter);
367 }
368 }
369
370 // http://code.spip.net/@spip_pg_alter_drop
371 function spip_pg_alter_drop($table, $arg, $serveur = '', $requeter = true) {
372 if (!preg_match('/^(COLUMN|INDEX|KEY|PRIMARY\s+KEY|)\s*`?(\w*)`?/', $arg, $r)) {
373 spip_log("alter drop: $arg incompris", 'pg.' . _LOG_ERREUR);
374 } else {
375 if (!$r[1] or $r[1] == 'COLUMN') {
376 return spip_pg_query("ALTER TABLE $table DROP " . $r[2], $serveur);
377 } elseif ($r[1][0] == 'P') {
378 return spip_pg_query("ALTER TABLE $table DROP CONSTRAINT $table" . '_pkey', $serveur);
379 } else {
380 return spip_pg_query("DROP INDEX " . $table . '_' . $r[2], $serveur);
381 }
382 }
383 }
384
385 function spip_pg_alter_modify($table, $arg, $serveur = '', $requeter = true) {
386 if (!preg_match('/^`?(\w+)`?\s+(.*)$/', $arg, $r)) {
387 spip_log("alter modify: $arg incompris", 'pg.' . _LOG_ERREUR);
388 } else {
389 return spip_pg_alter_change($table, $r[1] . ' ' . $arg, $serveur = '', $requeter = true);
390 }
391 }
392
393 // attention (en pg) :
394 // - alter table A rename to X = changer le nom de la table
395 // - alter table A rename X to Y = changer le nom de la colonne X en Y
396 // pour l'instant, traiter simplement RENAME TO X
397 function spip_pg_alter_rename($table, $arg, $serveur = '', $requeter = true) {
398 $rename = "";
399 // si TO, mais pas au debut
400 if (!stripos($arg, 'TO ')) {
401 $rename = $arg;
402 } elseif (preg_match('/^(TO)\s*`?(\w*)`?/', $arg, $r)) {
403 $rename = $r[2];
404 } else {
405 spip_log("alter rename: $arg incompris", 'pg.' . _LOG_ERREUR);
406 }
407
408 return $rename ? spip_pg_query("ALTER TABLE $table RENAME TO $rename") : false;
409 }
410
411
412 /**
413 * Fonction de creation d'un INDEX
414 *
415 * @param string $nom : nom de l'index
416 * @param string $table : table sql de l'index
417 * @param string /array $champs : liste de champs sur lesquels s'applique l'index
418 * @param string $serveur : nom de la connexion sql utilisee
419 * @param bool $requeter : true pour executer la requete ou false pour retourner le texte de la requete
420 *
421 * @return bool ou requete
422 */
423 function spip_pg_create_index($nom, $table, $champs, $serveur = '', $requeter = true) {
424 if (!($nom or $table or $champs)) {
425 spip_log("Champ manquant pour creer un index pg ($nom, $table, (" . @join(',', $champs) . "))",
426 'pg.' . _LOG_ERREUR);
427
428 return false;
429 }
430
431 $nom = str_replace("`", "", $nom);
432 $champs = str_replace("`", "", $champs);
433
434 // PG ne differentie pas noms des index en fonction des tables
435 // il faut donc creer des noms uniques d'index pour une base pg
436 $nom = $table . '_' . $nom;
437 // enlever d'eventuelles parentheses deja presentes sur champs
438 if (!is_array($champs)) {
439 if ($champs[0] == "(") {
440 $champs = substr($champs, 1, -1);
441 }
442 $champs = array($champs);
443 }
444 $query = "CREATE INDEX $nom ON $table (" . join(',', $champs) . ")";
445 if (!$requeter) {
446 return $query;
447 }
448 $res = spip_pg_query($query, $serveur, $requeter);
449
450 return $res;
451 }
452
453
454 // http://code.spip.net/@spip_pg_explain
455 function spip_pg_explain($query, $serveur = '', $requeter = true) {
456 if (strpos(ltrim($query), 'SELECT') !== 0) {
457 return array();
458 }
459 $connexion = &$GLOBALS['connexions'][$serveur ? strtolower($serveur) : 0];
460 $prefixe = $connexion['prefixe'];
461 $link = $connexion['link'];
462 if (preg_match('/\s(SET|VALUES|WHERE)\s/i', $query, $regs)) {
463 $suite = strstr($query, $regs[0]);
464 $query = substr($query, 0, -strlen($suite));
465 } else {
466 $suite = '';
467 }
468 $query = 'EXPLAIN ' . preg_replace('/([,\s])spip_/', '\1' . $prefixe . '_', $query) . $suite;
469
470 if (!$requeter) {
471 return $query;
472 }
473 $r = spip_pg_query_simple($link, $query);
474
475 return spip_pg_fetch($r, null, $serveur);
476 }
477
478
479 /**
480 * Sélectionne une base de données
481 *
482 * @param string $db
483 * Nom de la base à utiliser
484 * @param string $serveur
485 * Nom du connecteur
486 * @param bool $requeter
487 * Inutilisé
488 *
489 * @return bool|string
490 * - Nom de la base en cas de succès.
491 * - False en cas d'erreur.
492 **/
493 function spip_pg_selectdb($db, $serveur = '', $requeter = true) {
494 // se connecter a la base indiquee
495 // avec les identifiants connus
496 $index = $serveur ? strtolower($serveur) : 0;
497
498 if ($link = spip_connect_db('', '', '', '', $db, 'pg', '', '')) {
499 if (($db == $link['db']) && $GLOBALS['connexions'][$index] = $link) {
500 return $db;
501 }
502 } else {
503 return false;
504 }
505 }
506
507 // Qu'une seule base pour le moment
508
509 // http://code.spip.net/@spip_pg_listdbs
510 function spip_pg_listdbs($serveur) {
511 $connexion = &$GLOBALS['connexions'][$serveur ? strtolower($serveur) : 0];
512 $link = $connexion['link'];
513 $dbs = array();
514 $res = spip_pg_query_simple($link, "select * From pg_database");
515 while ($row = pg_fetch_array($res, null, PGSQL_NUM)) {
516 $dbs[] = reset($row);
517 }
518
519 return $dbs;
520 }
521
522 // http://code.spip.net/@spip_pg_select
523 function spip_pg_select(
524 $select,
525 $from,
526 $where = '',
527 $groupby = array(),
528 $orderby = '',
529 $limit = '',
530 $having = '',
531 $serveur = '',
532 $requeter = true
533 ) {
534
535 $connexion = &$GLOBALS['connexions'][$serveur ? strtolower($serveur) : 0];
536 $prefixe = $connexion['prefixe'];
537 $link = $connexion['link'];
538 $db = $connexion['db'];
539
540 $limit = preg_match("/^\s*(([0-9]+),)?\s*([0-9]+)\s*$/", $limit, $limatch);
541 if ($limit) {
542 $offset = $limatch[2];
543 $count = $limatch[3];
544 }
545
546 $select = spip_pg_frommysql($select);
547
548 // si pas de tri explicitement demande, le GROUP BY ne
549 // contient que la clef primaire.
550 // lui ajouter alors le champ de tri par defaut
551 if (preg_match("/FIELD\(([a-z]+\.[a-z]+),/i", $orderby[0], $groupbyplus)) {
552 $groupby[] = $groupbyplus[1];
553 }
554
555 $orderby = spip_pg_orderby($orderby, $select);
556
557 if ($having) {
558 if (is_array($having)) {
559 $having = join("\n\tAND ", array_map('calculer_pg_where', $having));
560 }
561 }
562 $from = spip_pg_from($from, $prefixe);
563 $query = "SELECT " . $select
564 . (!$from ? '' : "\nFROM $from")
565 . (!$where ? '' : ("\nWHERE " . (!is_array($where) ? calculer_pg_where($where) : (join("\n\tAND ",
566 array_map('calculer_pg_where', $where))))))
567 . spip_pg_groupby($groupby, $from, $select)
568 . (!$having ? '' : "\nHAVING $having")
569 . ($orderby ? ("\nORDER BY $orderby") : '')
570 . (!$limit ? '' : (" LIMIT $count" . (!$offset ? '' : " OFFSET $offset")));
571
572 // renvoyer la requete inerte si demandee
573 if ($requeter === false) {
574 return $query;
575 }
576
577 $r = spip_pg_trace_query($query, $serveur);
578
579 return $r ? $r : $query;;
580 }
581
582 // Le traitement des prefixes de table dans un Select se limite au FROM
583 // car le reste de la requete utilise les alias (AS) systematiquement
584
585 // http://code.spip.net/@spip_pg_from
586 function spip_pg_from($from, $prefixe) {
587 if (is_array($from)) {
588 $from = spip_pg_select_as($from);
589 }
590
591 return !$prefixe ? $from : preg_replace('/(\b)spip_/', '\1' . $prefixe . '_', $from);
592 }
593
594 // http://code.spip.net/@spip_pg_orderby
595 function spip_pg_orderby($order, $select) {
596 $res = array();
597 $arg = (is_array($order) ? $order : preg_split('/\s*,\s*/', $order));
598
599 foreach ($arg as $v) {
600 if (preg_match('/(case\s+.*?else\s+0\s+end)\s*AS\s+' . $v . '/', $select, $m)) {
601
602 $res[] = $m[1];
603 } else {
604 $res[] = $v;
605 }
606 }
607
608 return spip_pg_frommysql(join(',', $res));
609 }
610
611 // Conversion a l'arrach' des jointures MySQL en jointures PG
612 // A refaire pour tirer parti des possibilites de PG et de MySQL5
613 // et pour enlever les repetitions (sans incidence de perf, mais ca fait sale)
614
615 // http://code.spip.net/@spip_pg_groupby
616 function spip_pg_groupby($groupby, $from, $select) {
617 $join = strpos($from, ",");
618 // ismplifier avant de decouper
619 if (is_string($select)) // fct SQL sur colonne et constante apostrophee ==> la colonne
620 {
621 $select = preg_replace('/\w+\(\s*([^(),\']*),\s*\'[^\']*\'[^)]*\)/', '\\1', $select);
622 }
623
624 if ($join or $groupby) {
625 $join = is_array($select) ? $select : explode(", ", $select);
626 }
627 if ($join) {
628 // enlever les 0 as points, '', ...
629 foreach ($join as $k => $v) {
630 $v = str_replace('DISTINCT ', '', $v);
631 // fct SQL sur colonne et constante apostrophee ==> la colonne
632 $v = preg_replace('/\w+\(\s*([^(),\']*),\s*\'[^\']*\'[^)]*\)/', '\\1', $v);
633 $v = preg_replace('/CAST\(\s*([^(),\' ]*\s+)as\s*\w+\)/', '\\1', $v);
634 // resultat d'agregat ne sont pas a mettre dans le groupby
635 $v = preg_replace('/(SUM|COUNT|MAX|MIN|UPPER)\([^)]+\)(\s*AS\s+\w+)\s*,?/i', '', $v);
636 // idem sans AS (fetch numerique)
637 $v = preg_replace('/(SUM|COUNT|MAX|MIN|UPPER)\([^)]+\)\s*,?/i', '', $v);
638 // des AS simples : on garde le cote droit du AS
639 $v = preg_replace('/^.*\sAS\s+(\w+)\s*$/i', '\\1', $v);
640 // ne reste plus que les vrais colonnes, ou des constantes a virer
641 if (preg_match(',^[\'"],', $v) or is_numeric($v)) {
642 unset($join[$k]);
643 } else {
644 $join[$k] = trim($v);
645 }
646 }
647 $join = array_diff($join, array(''));
648 $join = implode(',', $join);
649 }
650 if (is_array($groupby)) {
651 $groupby = join(',', $groupby);
652 }
653 if ($join) {
654 $groupby = $groupby ? "$groupby, $join" : $join;
655 }
656 if (!$groupby) {
657 return '';
658 }
659
660 $groupby = spip_pg_frommysql($groupby);
661 // Ne pas mettre dans le Group-By des valeurs numeriques
662 // issue de prepare_recherche
663 $groupby = preg_replace('/^\s*\d+\s+AS\s+\w+\s*,?\s*/i', '', $groupby);
664 $groupby = preg_replace('/,\s*\d+\s+AS\s+\w+\s*/i', '', $groupby);
665 $groupby = preg_replace('/\s+AS\s+\w+\s*/i', '', $groupby);
666
667 return "\nGROUP BY $groupby";
668 }
669
670 // Conversion des operateurs MySQL en PG
671 // IMPORTANT: "0+X" est vu comme conversion numerique du debut de X
672 // Les expressions de date ne sont pas gerees au-dela de 3 ()
673 // Le 'as' du 'CAST' est en minuscule pour echapper au dernier preg_replace
674 // de spip_pg_groupby.
675 // A ameliorer.
676
677 // http://code.spip.net/@spip_pg_frommysql
678 function spip_pg_frommysql($arg) {
679 if (is_array($arg)) {
680 $arg = join(", ", $arg);
681 }
682
683 $res = spip_pg_fromfield($arg);
684
685 $res = preg_replace('/\brand[(][)]/i', 'random()', $res);
686
687 $res = preg_replace('/\b0\.0[+]([a-zA-Z0-9_.]+)\s*/',
688 'CAST(substring(\1, \'^ *[0-9.]+\') as float)',
689 $res);
690 $res = preg_replace('/\b0[+]([a-zA-Z0-9_.]+)\s*/',
691 'CAST(substring(\1, \'^ *[0-9]+\') as int)',
692 $res);
693 $res = preg_replace('/\bconv[(]([^,]*)[^)]*[)]/i',
694 'CAST(substring(\1, \'^ *[0-9]+\') as int)',
695 $res);
696
697 $res = preg_replace('/UNIX_TIMESTAMP\s*[(]\s*[)]/',
698 ' EXTRACT(epoch FROM NOW())', $res);
699
700 // la fonction md5(integer) n'est pas connu en pg
701 // il faut donc forcer les types en text (cas de md5(id_article))
702 $res = preg_replace('/md5\s*[(]([^)]*)[)]/i',
703 'MD5(CAST(\1 AS text))', $res);
704
705 $res = preg_replace('/UNIX_TIMESTAMP\s*[(]([^)]*)[)]/',
706 ' EXTRACT(epoch FROM \1)', $res);
707
708 $res = preg_replace('/\bDAYOFMONTH\s*[(]([^()]*([(][^()]*[)][^()]*)*[^)]*)[)]/',
709 ' EXTRACT(day FROM \1)',
710 $res);
711
712 $res = preg_replace('/\bMONTH\s*[(]([^()]*([(][^)]*[)][^()]*)*[^)]*)[)]/',
713 ' EXTRACT(month FROM \1)',
714 $res);
715
716 $res = preg_replace('/\bYEAR\s*[(]([^()]*([(][^)]*[)][^()]*)*[^)]*)[)]/',
717 ' EXTRACT(year FROM \1)',
718 $res);
719
720 $res = preg_replace('/TO_DAYS\s*[(]([^()]*([(][^)]*[)][()]*)*)[)]/',
721 ' EXTRACT(day FROM \1 - \'0001-01-01\')',
722 $res);
723
724 $res = preg_replace("/(EXTRACT[(][^ ]* FROM *)\"([^\"]*)\"/", '\1\'\2\'', $res);
725
726 $res = preg_replace('/DATE_FORMAT\s*[(]([^,]*),\s*\'%Y%m%d\'[)]/', 'to_char(\1, \'YYYYMMDD\')', $res);
727
728 $res = preg_replace('/DATE_FORMAT\s*[(]([^,]*),\s*\'%Y%m\'[)]/', 'to_char(\1, \'YYYYMM\')', $res);
729
730 $res = preg_replace('/DATE_SUB\s*[(]([^,]*),/', '(\1 -', $res);
731 $res = preg_replace('/DATE_ADD\s*[(]([^,]*),/', '(\1 +', $res);
732 $res = preg_replace('/INTERVAL\s+(\d+\s+\w+)/', 'INTERVAL \'\1\'', $res);
733 $res = preg_replace('/([+<>-]=?)\s*(\'\d+-\d+-\d+\s+\d+:\d+(:\d+)\')/', '\1 timestamp \2', $res);
734 $res = preg_replace('/(\'\d+-\d+-\d+\s+\d+:\d+:\d+\')\s*([+<>-]=?)/', 'timestamp \1 \2', $res);
735
736 $res = preg_replace('/([+<>-]=?)\s*(\'\d+-\d+-\d+\')/', '\1 timestamp \2', $res);
737 $res = preg_replace('/(\'\d+-\d+-\d+\')\s*([+<>-]=?)/', 'timestamp \1 \2', $res);
738
739 $res = preg_replace('/(timestamp .\d+)-00-/', '\1-01-', $res);
740 $res = preg_replace('/(timestamp .\d+-\d+)-00/', '\1-01', $res);
741 # correct en theorie mais produit des debordements arithmetiques
742 # $res = preg_replace("/(EXTRACT[(][^ ]* FROM *)(timestamp *'[^']*' *[+-] *timestamp *'[^']*') *[)]/", '\2', $res);
743 $res = preg_replace("/(EXTRACT[(][^ ]* FROM *)('[^']*')/", '\1 timestamp \2', $res);
744 $res = preg_replace("/\sLIKE\s+/", ' ILIKE ', $res);
745
746 return str_replace('REGEXP', '~', $res);
747 }
748
749 // http://code.spip.net/@spip_pg_fromfield
750 function spip_pg_fromfield($arg) {
751 while (preg_match('/^(.*?)FIELD\s*\(([^,]*)((,[^,)]*)*)\)/', $arg, $m)) {
752
753 preg_match_all('/,([^,]*)/', $m[3], $r, PREG_PATTERN_ORDER);
754 $res = '';
755 $n = 0;
756 $index = $m[2];
757 foreach ($r[1] as $v) {
758 $n++;
759 $res .= "\nwhen $index=$v then $n";
760 }
761 $arg = $m[1] . "case $res else 0 end "
762 . substr($arg, strlen($m[0]));
763 }
764
765 return $arg;
766 }
767
768 // http://code.spip.net/@calculer_pg_where
769 function calculer_pg_where($v) {
770 if (!is_array($v)) {
771 return spip_pg_frommysql($v);
772 }
773
774 $op = str_replace('REGEXP', '~', array_shift($v));
775 if (!($n = count($v))) {
776 return $op;
777 } else {
778 $arg = calculer_pg_where(array_shift($v));
779 if ($n == 1) {
780 return "$op($arg)";
781 } else {
782 $arg2 = calculer_pg_where(array_shift($v));
783 if ($n == 2) {
784 return "($arg $op $arg2)";
785 } else {
786 return "($arg $op ($arg2) : $v[0])";
787 }
788 }
789 }
790 }
791
792
793 // http://code.spip.net/@calculer_pg_expression
794 function calculer_pg_expression($expression, $v, $join = 'AND') {
795 if (empty($v)) {
796 return '';
797 }
798
799 $exp = "\n$expression ";
800
801 if (!is_array($v)) {
802 $v = array($v);
803 }
804
805 if (strtoupper($join) === 'AND') {
806 return $exp . join("\n\t$join ", array_map('calculer_pg_where', $v));
807 } else {
808 return $exp . join($join, $v);
809 }
810 }
811
812 // http://code.spip.net/@spip_pg_select_as
813 function spip_pg_select_as($args) {
814 $argsas = "";
815 foreach ($args as $k => $v) {
816 if (substr($k, -1) == '@') {
817 // c'est une jointure qui se refere au from precedent
818 // pas de virgule
819 $argsas .= ' ' . $v;
820 } else {
821 $as = '';
822 // spip_log("$k : $v", _LOG_DEBUG);
823 if (!is_numeric($k)) {
824 if (preg_match('/\.(.*)$/', $k, $r)) {
825 $v = $k;
826 } elseif ($v != $k) {
827 $p = strpos($v, " ");
828 if ($p) {
829 $v = substr($v, 0, $p) . " AS $k" . substr($v, $p);
830 } else {
831 $as = " AS $k";
832 }
833 }
834 }
835 // spip_log("subs $k : $v avec $as", _LOG_DEBUG);
836 // if (strpos($v, 'JOIN') === false) $argsas .= ', ';
837 $argsas .= ', ' . $v . $as;
838 }
839 }
840
841 return substr($argsas, 2);
842 }
843
844 // http://code.spip.net/@spip_pg_fetch
845 function spip_pg_fetch($res, $t = '', $serveur = '', $requeter = true) {
846
847 if ($res) {
848 $res = pg_fetch_array($res, null, PGSQL_ASSOC);
849 }
850
851 return $res;
852 }
853
854 function spip_pg_seek($r, $row_number, $serveur = '', $requeter = true) {
855 if ($r) {
856 return pg_result_seek($r, $row_number);
857 }
858 }
859
860
861 // http://code.spip.net/@spip_pg_countsel
862 function spip_pg_countsel(
863 $from = array(),
864 $where = array(),
865 $groupby = array(),
866 $having = array(),
867 $serveur = '',
868 $requeter = true
869 ) {
870 $c = !$groupby ? '*' : ('DISTINCT ' . (is_string($groupby) ? $groupby : join(',', $groupby)));
871 $r = spip_pg_select("COUNT($c)", $from, $where, '', '', '', $having, $serveur, $requeter);
872 if (!$requeter) {
873 return $r;
874 }
875 if (!is_resource($r)) {
876 return 0;
877 }
878 list($c) = pg_fetch_array($r, null, PGSQL_NUM);
879
880 return $c;
881 }
882
883 // http://code.spip.net/@spip_pg_count
884 function spip_pg_count($res, $serveur = '', $requeter = true) {
885 return !$res ? 0 : pg_numrows($res);
886 }
887
888 // http://code.spip.net/@spip_pg_free
889 function spip_pg_free($res, $serveur = '', $requeter = true) {
890 // rien a faire en postgres
891 }
892
893 // http://code.spip.net/@spip_pg_delete
894 function spip_pg_delete($table, $where = '', $serveur = '', $requeter = true) {
895
896 $connexion = &$GLOBALS['connexions'][$serveur ? strtolower($serveur) : 0];
897 $table = prefixer_table_spip($table, $connexion['prefixe']);
898
899 $query = calculer_pg_expression('DELETE FROM', $table, ',')
900 . calculer_pg_expression('WHERE', $where, 'AND');
901
902 // renvoyer la requete inerte si demandee
903 if (!$requeter) {
904 return $query;
905 }
906
907 $res = spip_pg_trace_query($query, $serveur);
908 if ($res) {
909 return pg_affected_rows($res);
910 } else {
911 return false;
912 }
913 }
914
915 // http://code.spip.net/@spip_pg_insert
916 function spip_pg_insert($table, $champs, $valeurs, $desc = array(), $serveur = '', $requeter = true) {
917 $connexion = &$GLOBALS['connexions'][$serveur ? strtolower($serveur) : 0];
918 $prefixe = $connexion['prefixe'];
919 $link = $connexion['link'];
920
921 if (!$desc) {
922 $desc = description_table($table, $serveur);
923 }
924 $seq = spip_pg_sequence($table, true);
925 // si pas de cle primaire dans l'insertion, renvoyer curval
926 if (!preg_match(",\b$seq\b,", $champs)) {
927 $seq = spip_pg_sequence($table);
928 $seq = prefixer_table_spip($seq, $prefixe);
929 $seq = "currval('$seq')";
930 }
931
932 $table = prefixer_table_spip($table, $prefixe);
933 $ret = !$seq ? '' : (" RETURNING $seq");
934 $ins = (strlen($champs) < 3)
935 ? " DEFAULT VALUES"
936 : "$champs VALUES $valeurs";
937 $q = "INSERT INTO $table $ins $ret";
938 if (!$requeter) {
939 return $q;
940 }
941 $connexion['last'] = $q;
942 $r = spip_pg_query_simple($link, $q);
943 # spip_log($q,'pg.'._LOG_DEBUG);
944 if ($r) {
945 if (!$ret) {
946 return 0;
947 }
948 if ($r2 = pg_fetch_array($r, null, PGSQL_NUM)) {
949 return $r2[0];
950 }
951 }
952
953 return false;
954 }
955
956 // http://code.spip.net/@spip_pg_insertq
957 function spip_pg_insertq($table, $couples = array(), $desc = array(), $serveur = '', $requeter = true) {
958
959 if (!$desc) {
960 $desc = description_table($table, $serveur);
961 }
962 if (!$desc) {
963 die("$table insertion sans description");
964 }
965 $fields = $desc['field'];
966
967 foreach ($couples as $champ => $val) {
968 $couples[$champ] = spip_pg_cite($val, $fields[$champ]);
969 }
970
971 // recherche de champs 'timestamp' pour mise a jour auto de ceux-ci
972 $couples = spip_pg_ajouter_champs_timestamp($table, $couples, $desc, $serveur);
973
974 return spip_pg_insert($table, "(" . join(',', array_keys($couples)) . ")", "(" . join(',', $couples) . ")", $desc,
975 $serveur, $requeter);
976 }
977
978
979 // http://code.spip.net/@spip_pg_insertq_multi
980 function spip_pg_insertq_multi($table, $tab_couples = array(), $desc = array(), $serveur = '', $requeter = true) {
981
982 if (!$desc) {
983 $desc = description_table($table, $serveur);
984 }
985 if (!$desc) {
986 die("$table insertion sans description");
987 }
988 $fields = isset($desc['field']) ? $desc['field'] : array();
989
990 // recherche de champs 'timestamp' pour mise a jour auto de ceux-ci
991 // une premiere fois pour ajouter maj dans les cles
992 $c = isset($tab_couples[0]) ? $tab_couples[0] : array();
993 $les_cles = spip_pg_ajouter_champs_timestamp($table, $c, $desc, $serveur);
994
995 $cles = "(" . join(',', array_keys($les_cles)) . ')';
996 $valeurs = array();
997 foreach ($tab_couples as $couples) {
998 foreach ($couples as $champ => $val) {
999 $couples[$champ] = spip_pg_cite($val, $fields[$champ]);
1000 }
1001 // recherche de champs 'timestamp' pour mise a jour auto de ceux-ci
1002 $couples = spip_pg_ajouter_champs_timestamp($table, $couples, $desc, $serveur);
1003
1004 $valeurs[] = '(' . join(',', $couples) . ')';
1005 }
1006 $valeurs = implode(', ', $valeurs);
1007
1008 return spip_pg_insert($table, $cles, $valeurs, $desc, $serveur, $requeter);
1009 }
1010
1011
1012 // http://code.spip.net/@spip_pg_update
1013 function spip_pg_update($table, $couples, $where = '', $desc = '', $serveur = '', $requeter = true) {
1014
1015 if (!$couples) {
1016 return;
1017 }
1018 $connexion = $GLOBALS['connexions'][$serveur ? strtolower($serveur) : 0];
1019 $table = prefixer_table_spip($table, $connexion['prefixe']);
1020
1021 // recherche de champs 'timestamp' pour mise a jour auto de ceux-ci
1022 $couples = spip_pg_ajouter_champs_timestamp($table, $couples, $desc, $serveur);
1023
1024 $set = array();
1025 foreach ($couples as $champ => $val) {
1026 $set[] = $champ . '=' . $val;
1027 }
1028
1029 $query = calculer_pg_expression('UPDATE', $table, ',')
1030 . calculer_pg_expression('SET', $set, ',')
1031 . calculer_pg_expression('WHERE', $where, 'AND');
1032
1033 // renvoyer la requete inerte si demandee
1034 if (!$requeter) {
1035 return $query;
1036 }
1037
1038 return spip_pg_trace_query($query, $serveur);
1039 }
1040
1041 // idem, mais les valeurs sont des constantes a mettre entre apostrophes
1042 // sauf les expressions de date lorsqu'il s'agit de fonctions SQL (NOW etc)
1043 // http://code.spip.net/@spip_pg_updateq
1044 function spip_pg_updateq($table, $couples, $where = '', $desc = array(), $serveur = '', $requeter = true) {
1045 if (!$couples) {
1046 return;
1047 }
1048 if (!$desc) {
1049 $desc = description_table($table, $serveur);
1050 }
1051 $fields = $desc['field'];
1052 foreach ($couples as $k => $val) {
1053 $couples[$k] = spip_pg_cite($val, $fields[$k]);
1054 }
1055
1056 return spip_pg_update($table, $couples, $where, $desc, $serveur, $requeter);
1057 }
1058
1059
1060 // http://code.spip.net/@spip_pg_replace
1061 function spip_pg_replace($table, $values, $desc, $serveur = '', $requeter = true) {
1062 if (!$values) {
1063 spip_log("replace vide $table", 'pg.' . _LOG_AVERTISSEMENT);
1064
1065 return 0;
1066 }
1067 $connexion = &$GLOBALS['connexions'][$serveur ? strtolower($serveur) : 0];
1068 $prefixe = $connexion['prefixe'];
1069 $link = $connexion['link'];
1070
1071 if (!$desc) {
1072 $desc = description_table($table, $serveur);
1073 }
1074 if (!$desc) {
1075 die("$table insertion sans description");
1076 }
1077 $prim = $desc['key']['PRIMARY KEY'];
1078 $ids = preg_split('/,\s*/', $prim);
1079 $noprims = $prims = array();
1080 foreach ($values as $k => $v) {
1081 $values[$k] = $v = spip_pg_cite($v, $desc['field'][$k]);
1082
1083 if (!in_array($k, $ids)) {
1084 $noprims[$k] = "$k=$v";
1085 } else {
1086 $prims[$k] = "$k=$v";
1087 }
1088 }
1089
1090 // recherche de champs 'timestamp' pour mise a jour auto de ceux-ci
1091 $values = spip_pg_ajouter_champs_timestamp($table, $values, $desc, $serveur);
1092
1093 $where = join(' AND ', $prims);
1094 if (!$where) {
1095 return spip_pg_insert($table, "(" . join(',', array_keys($values)) . ")", "(" . join(',', $values) . ")", $desc,
1096 $serveur);
1097 }
1098 $couples = join(',', $noprims);
1099
1100 $seq = spip_pg_sequence($table);
1101 $table = prefixer_table_spip($table, $prefixe);
1102 $seq = prefixer_table_spip($seq, $prefixe);
1103
1104 $connexion['last'] = $q = "UPDATE $table SET $couples WHERE $where";
1105 if ($couples) {
1106 $couples = spip_pg_query_simple($link, $q);
1107 # spip_log($q,'pg.'._LOG_DEBUG);
1108 if (!$couples) {
1109 return false;
1110 }
1111 $couples = pg_affected_rows($couples);
1112 }
1113 if (!$couples) {
1114 $ret = !$seq ? '' :
1115 (" RETURNING nextval('$seq') < $prim");
1116 $connexion['last'] = $q = "INSERT INTO $table (" . join(',', array_keys($values)) . ') VALUES (' . join(',',
1117 $values) . ")$ret";
1118 $couples = spip_pg_query_simple($link, $q);
1119 if (!$couples) {
1120 return false;
1121 } elseif ($ret) {
1122 $r = pg_fetch_array($couples, null, PGSQL_NUM);
1123 if ($r[0]) {
1124 $connexion['last'] = $q = "SELECT setval('$seq', $prim) from $table";
1125 // Le code de SPIP met parfois la sequence a 0 (dans l'import)
1126 // MySQL n'en dit rien, on fait pareil pour PG
1127 $r = @pg_query($link, $q);
1128 }
1129 }
1130 }
1131
1132 return $couples;
1133 }
1134
1135
1136 // http://code.spip.net/@spip_pg_replace_multi
1137 function spip_pg_replace_multi($table, $tab_couples, $desc = array(), $serveur = '', $requeter = true) {
1138 // boucler pour traiter chaque requete independemment
1139 foreach ($tab_couples as $couples) {
1140 $retour = spip_pg_replace($table, $couples, $desc, $serveur, $requeter);
1141 }
1142
1143 // renvoie le dernier id
1144 return $retour;
1145 }
1146
1147
1148 // Donne la sequence eventuelle associee a une table
1149 // Pas extensible pour le moment,
1150
1151 // http://code.spip.net/@spip_pg_sequence
1152 function spip_pg_sequence($table, $raw = false) {
1153
1154 include_spip('base/serial');
1155 if (!isset($GLOBALS['tables_principales'][$table])) {
1156 return false;
1157 }
1158 $desc = $GLOBALS['tables_principales'][$table];
1159 $prim = @$desc['key']['PRIMARY KEY'];
1160 if (!preg_match('/^\w+$/', $prim)
1161 or strpos($desc['field'][$prim], 'int') === false
1162 ) {
1163 return '';
1164 } else {
1165 return $raw ? $prim : $table . '_' . $prim . "_seq";
1166 }
1167 }
1168
1169 // Explicite les conversions de Mysql d'une valeur $v de type $t
1170 // Dans le cas d'un champ date, pas d'apostrophe, c'est une syntaxe ad hoc
1171
1172 // http://code.spip.net/@spip_pg_cite
1173 function spip_pg_cite($v, $t) {
1174 if (is_null($v)) {
1175 return 'NULL';
1176 } // null php se traduit en NULL SQL
1177
1178 if (sql_test_date($t)) {
1179 if ($v and (strpos("0123456789", $v[0]) === false)) {
1180 return spip_pg_frommysql($v);
1181 } else {
1182 if (strncmp($v, '0000', 4) == 0) {
1183 $v = "0001" . substr($v, 4);
1184 }
1185 if (strpos($v, "-00-00") === 4) {
1186 $v = substr($v, 0, 4) . "-01-01" . substr($v, 10);
1187 }
1188
1189 return "timestamp '$v'";
1190 }
1191 } elseif (!sql_test_int($t)) {
1192 return ("'" . pg_escape_string($v) . "'");
1193 } elseif (is_numeric($v) or (strpos($v, 'CAST(') === 0)) {
1194 return $v;
1195 } elseif ($v[0] == '0' and $v[1] !== 'x' and ctype_xdigit(substr($v, 1))) {
1196 return substr($v, 1);
1197 } else {
1198 spip_log("Warning: '$v' n'est pas de type $t", 'pg.' . _LOG_AVERTISSEMENT);
1199
1200 return intval($v);
1201 }
1202 }
1203
1204 // http://code.spip.net/@spip_pg_hex
1205 function spip_pg_hex($v) {
1206 return "CAST(x'" . $v . "' as bigint)";
1207 }
1208
1209 function spip_pg_quote($v, $type = '') {
1210 if (!is_array($v)) {
1211 return spip_pg_cite($v, $type);
1212 }
1213 // si c'est un tableau, le parcourir en propageant le type
1214 foreach ($v as $k => $r) {
1215 $v[$k] = spip_pg_quote($r, $type);
1216 }
1217
1218 return join(",", $v);
1219 }
1220
1221 function spip_pg_date_proche($champ, $interval, $unite) {
1222 return '('
1223 . $champ
1224 . (($interval <= 0) ? '>' : '<')
1225 . (($interval <= 0) ? 'DATE_SUB' : 'DATE_ADD')
1226 . '('
1227 . sql_quote(date('Y-m-d H:i:s'))
1228 . ', INTERVAL '
1229 . (($interval > 0) ? $interval : (0 - $interval))
1230 . ' '
1231 . $unite
1232 . '))';
1233 }
1234
1235 // http://code.spip.net/@spip_pg_in
1236 function spip_pg_in($val, $valeurs, $not = '', $serveur) {
1237 //
1238 // IN (...) souvent limite a 255 elements, d'ou cette fonction assistante
1239 //
1240 // s'il n'y a pas de valeur, eviter de produire un IN vide: PG rale.
1241 if (!$valeurs) {
1242 return $not ? '0=0' : '0=1';
1243 }
1244 if (strpos($valeurs, "CAST(x'") !== false) {
1245 return "($val=" . join("OR $val=", explode(',', $valeurs)) . ')';
1246 }
1247 $n = $i = 0;
1248 $in_sql = "";
1249 while ($n = strpos($valeurs, ',', $n + 1)) {
1250 if ((++$i) >= 255) {
1251 $in_sql .= "($val $not IN (" .
1252 substr($valeurs, 0, $n) .
1253 "))\n" .
1254 ($not ? "AND\t" : "OR\t");
1255 $valeurs = substr($valeurs, $n + 1);
1256 $i = $n = 0;
1257 }
1258 }
1259 $in_sql .= "($val $not IN ($valeurs))";
1260
1261 return "($in_sql)";
1262 }
1263
1264 // http://code.spip.net/@spip_pg_error
1265 function spip_pg_error($query = '', $serveur, $requeter = true) {
1266 $link = $GLOBALS['connexions'][$serveur ? strtolower($serveur) : 0]['link'];
1267 $s = $link ? pg_last_error($link) : pg_last_error();
1268 if ($s) {
1269 $s = str_replace('ERROR', 'errcode: 1000 ', $s);
1270 spip_log("$s - $query", 'pg.' . _LOG_ERREUR);
1271 }
1272
1273 return $s;
1274 }
1275
1276 // http://code.spip.net/@spip_pg_errno
1277 function spip_pg_errno($serveur = '') {
1278 // il faudrait avoir la derniere ressource retournee et utiliser
1279 // http://fr2.php.net/manual/fr/function.pg-result-error.php
1280 return 0;
1281 }
1282
1283 // http://code.spip.net/@spip_pg_drop_table
1284 function spip_pg_drop_table($table, $exist = '', $serveur = '', $requeter = true) {
1285 if ($exist) {
1286 $exist = " IF EXISTS";
1287 }
1288 if (spip_pg_query("DROP TABLE$exist $table", $serveur, $requeter)) {
1289 return true;
1290 } else {
1291 return false;
1292 }
1293 }
1294
1295 // supprime une vue
1296 // http://code.spip.net/@spip_pg_drop_view
1297 function spip_pg_drop_view($view, $exist = '', $serveur = '', $requeter = true) {
1298 if ($exist) {
1299 $exist = " IF EXISTS";
1300 }
1301
1302 return spip_pg_query("DROP VIEW$exist $view", $serveur, $requeter);
1303 }
1304
1305 /**
1306 * Retourne une ressource de la liste des tables de la base de données
1307 *
1308 * @param string $match
1309 * Filtre sur tables à récupérer
1310 * @param string $serveur
1311 * Connecteur de la base
1312 * @param bool $requeter
1313 * true pour éxecuter la requête
1314 * false pour retourner le texte de la requête.
1315 * @return ressource
1316 * Ressource à utiliser avec sql_fetch()
1317 **/
1318 function spip_pg_showbase($match, $serveur = '', $requeter = true) {
1319 $connexion = &$GLOBALS['connexions'][$serveur ? strtolower($serveur) : 0];
1320 $link = $connexion['link'];
1321 $connexion['last'] = $q = "SELECT tablename FROM pg_tables WHERE tablename ILIKE " . _q($match);
1322
1323 return spip_pg_query_simple($link, $q);
1324 }
1325
1326 // http://code.spip.net/@spip_pg_showtable
1327 function spip_pg_showtable($nom_table, $serveur = '', $requeter = true) {
1328 $connexion = &$GLOBALS['connexions'][$serveur ? strtolower($serveur) : 0];
1329 $link = $connexion['link'];
1330 $connexion['last'] = $q = "SELECT column_name, column_default, data_type FROM information_schema.columns WHERE table_name ILIKE " . _q($nom_table);
1331
1332 $res = spip_pg_query_simple($link, $q);
1333 if (!$res) {
1334 return false;
1335 }
1336
1337 // etrangement, $res peut ne rien contenir, mais arriver ici...
1338 // il faut en tenir compte dans le return
1339 $fields = array();
1340 while ($field = pg_fetch_array($res, null, PGSQL_NUM)) {
1341 $fields[$field[0]] = $field[2] . (!$field[1] ? '' : (" DEFAULT " . $field[1]));
1342 }
1343 $connexion['last'] = $q = "SELECT indexdef FROM pg_indexes WHERE tablename ILIKE " . _q($nom_table);
1344 $res = spip_pg_query_simple($link, $q);
1345 $keys = array();
1346 while ($index = pg_fetch_array($res, null, PGSQL_NUM)) {
1347 if (preg_match('/CREATE\s+(UNIQUE\s+)?INDEX\s([^\s]+).*\((.*)\)$/', $index[0], $r)) {
1348 $nom = str_replace($nom_table . '_', '', $r[2]);
1349 $keys[($r[1] ? "PRIMARY KEY" : ("KEY " . $nom))] = $r[3];
1350 }
1351 }
1352
1353 return count($fields) ? array('field' => $fields, 'key' => $keys) : false;
1354 }
1355
1356 // Fonction de creation d'une table SQL nommee $nom
1357 // a partir de 2 tableaux PHP :
1358 // champs: champ => type
1359 // cles: type-de-cle => champ(s)
1360 // si $autoinc, c'est une auto-increment (i.e. serial) sur la Primary Key
1361 // Le nom des index est prefixe par celui de la table pour eviter les conflits
1362 // http://code.spip.net/@spip_pg_create
1363 function spip_pg_create($nom, $champs, $cles, $autoinc = false, $temporary = false, $serveur = '', $requeter = true) {
1364
1365 $connexion = $GLOBALS['connexions'][$serveur ? strtolower($serveur) : 0];
1366 $link = $connexion['link'];
1367 $nom = prefixer_table_spip($nom, $connexion['prefixe']);
1368
1369 $query = $prim = $prim_name = $v = $s = $p = '';
1370 $keys = array();
1371
1372 // certains plugins declarent les tables (permet leur inclusion dans le dump)
1373 // sans les renseigner (laisse le compilo recuperer la description)
1374 if (!is_array($champs) || !is_array($cles)) {
1375 return;
1376 }
1377
1378 foreach ($cles as $k => $v) {
1379 if (strpos($k, "KEY ") === 0) {
1380 $n = str_replace('`', '', $k);
1381 $v = str_replace('`', '"', $v);
1382 $i = $nom . preg_replace("/KEY +/", '_', $n);
1383 if ($k != $n) {
1384 $i = "\"$i\"";
1385 }
1386 $keys[] = "CREATE INDEX $i ON $nom ($v);";
1387 } elseif (strpos($k, "UNIQUE ") === 0) {
1388 $k = preg_replace("/^UNIQUE +/", '', $k);
1389 $prim .= "$s\n\t\tCONSTRAINT " . str_replace('`', '"', $k) . " UNIQUE ($v)";
1390 } else {
1391 $prim .= "$s\n\t\t" . str_replace('`', '"', $k) . " ($v)";
1392 }
1393 if ($k == "PRIMARY KEY") {
1394 $prim_name = $v;
1395 }
1396 $s = ",";
1397 }
1398 $s = '';
1399
1400 $character_set = "";
1401 if (@$GLOBALS['meta']['charset_sql_base']) {
1402 $character_set .= " CHARACTER SET " . $GLOBALS['meta']['charset_sql_base'];
1403 }
1404 if (@$GLOBALS['meta']['charset_collation_sql_base']) {
1405 $character_set .= " COLLATE " . $GLOBALS['meta']['charset_collation_sql_base'];
1406 }
1407
1408 foreach ($champs as $k => $v) {
1409 $k = str_replace('`', '"', $k);
1410 if (preg_match(',([a-z]*\s*(\(\s*[0-9]*\s*\))?(\s*binary)?),i', $v, $defs)) {
1411 if (preg_match(',(char|text),i', $defs[1]) and !preg_match(',binary,i', $defs[1])) {
1412 $v = $defs[1] . $character_set . ' ' . substr($v, strlen($defs[1]));
1413 }
1414 }
1415
1416 $query .= "$s\n\t\t$k "
1417 . (($autoinc && ($prim_name == $k) && preg_match(',\b(big|small|medium|tiny)?int\b,i', $v))
1418 ? " bigserial"
1419 : mysql2pg_type($v)
1420 );
1421 $s = ",";
1422 }
1423 $temporary = $temporary ? 'TEMPORARY' : '';
1424
1425 // En l'absence de "if not exists" en PG, on neutralise les erreurs
1426
1427 $q = "CREATE $temporary TABLE $nom ($query" . ($prim ? ",$prim" : '') . ")" .
1428 ($character_set ? " DEFAULT $character_set" : "")
1429 . "\n";
1430
1431 if (!$requeter) {
1432 return $q;
1433 }
1434 $connexion['last'] = $q;
1435 $r = @pg_query($link, $q);
1436
1437 if (!$r) {
1438 spip_log("Impossible de creer cette table: $q", 'pg.' . _LOG_ERREUR);
1439 } else {
1440 foreach ($keys as $index) {
1441 pg_query($link, $index);
1442 }
1443 }
1444
1445 return $r;
1446 }
1447
1448
1449 function spip_pg_create_base($nom, $serveur = '', $requeter = true) {
1450 return spip_pg_query("CREATE DATABASE $nom", $serveur, $requeter);
1451 }
1452
1453 // Fonction de creation d'une vue SQL nommee $nom
1454 // http://code.spip.net/@spip_pg_create_view
1455 function spip_pg_create_view($nom, $query_select, $serveur = '', $requeter = true) {
1456 if (!$query_select) {
1457 return false;
1458 }
1459 // vue deja presente
1460 if (sql_showtable($nom, false, $serveur)) {
1461 if ($requeter) {
1462 spip_log("Echec creation d'une vue sql ($nom) car celle-ci existe deja (serveur:$serveur)", 'pg.' . _LOG_ERREUR);
1463 }
1464
1465 return false;
1466 }
1467
1468 $query = "CREATE VIEW $nom AS " . $query_select;
1469
1470 return spip_pg_query($query, $serveur, $requeter);
1471 }
1472
1473
1474 // http://code.spip.net/@spip_pg_set_connect_charset
1475 function spip_pg_set_connect_charset($charset, $serveur = '', $requeter = true) {
1476 spip_log("changement de charset sql a ecrire en PG", 'pg.' . _LOG_ERREUR);
1477 }
1478
1479
1480 /**
1481 * Optimise une table SQL
1482 *
1483 * @param $table nom de la table a optimiser
1484 * @param $serveur nom de la connexion
1485 * @param $requeter effectuer la requete ? sinon retourner son code
1486 * @return bool|string true / false / requete
1487 **/
1488 // http://code.spip.net/@spip_sqlite_optimize
1489 function spip_pg_optimize($table, $serveur = '', $requeter = true) {
1490 return spip_pg_query("VACUUM " . $table, $serveur, $requeter);
1491 }
1492
1493 // Selectionner la sous-chaine dans $objet
1494 // correspondant a $lang. Cf balise Multi de Spip
1495
1496 // http://code.spip.net/@spip_pg_multi
1497 function spip_pg_multi($objet, $lang) {
1498 $r = "regexp_replace("
1499 . $objet
1500 . ",'<multi>.*[[]"
1501 . $lang
1502 . "[]]([^[]*).*</multi>', E'\\\\1') AS multi";
1503
1504 return $r;
1505 }
1506
1507 // Palanquee d'idiosyncrasies MySQL dans les creations de table
1508 // A completer par les autres, mais essayer de reduire en amont.
1509
1510 // http://code.spip.net/@mysql2pg_type
1511 function mysql2pg_type($v) {
1512 $remplace = array(
1513 '/auto_increment/i' => '', // non reconnu
1514 '/bigint/i' => 'bigint',
1515 '/mediumint/i' => 'mediumint',
1516 '/smallint/i' => 'smallint',
1517 "/tinyint/i" => 'int',
1518 '/int\s*[(]\s*\d+\s*[)]/i' => 'int',
1519 "/longtext/i" => 'text',
1520 "/mediumtext/i" => 'text',
1521 "/tinytext/i" => 'text',
1522 "/longblob/i" => 'text',
1523 "/0000-00-00/" => '0001-01-01',
1524 "/datetime/i" => 'timestamp',
1525 "/unsigned/i" => '',
1526 "/double/i" => 'double precision',
1527 '/VARCHAR\((\d+)\)\s+BINARY/i' => 'varchar(\1)',
1528 "/ENUM *[(][^)]*[)]/i" => "varchar(255)",
1529 '/(timestamp .* )ON .*$/is' => '\\1',
1530 );
1531
1532 return preg_replace(array_keys($remplace), array_values($remplace), $v);
1533 }
1534
1535 // Renvoie false si on n'a pas les fonctions pg (pour l'install)
1536 // http://code.spip.net/@spip_versions_pg
1537 function spip_versions_pg() {
1538 charger_php_extension('pgsql');
1539
1540 return function_exists('pg_connect');
1541 }