Файловый менеджер - Редактировать - /home/atelierpru/provence/52177/req.tar
Назад
sqlite3.php 0000666 00000002636 15245543621 0006663 0 ustar 00 <?php /***************************************************************************\ * SPIP, Systeme de publication pour l'internet * * * * Copyright (c) 2001-2019 * * Arnaud Martin, Antoine Pitrou, Philippe Riviere, Emmanuel Saint-James * * * * Ce programme est un logiciel libre distribue sous licence GNU/GPL. * * Pour plus de details voir le fichier COPYING.txt ou l'aide en ligne. * \***************************************************************************/ if (!defined('_ECRIRE_INC_VERSION')) { return; } include_spip('req/sqlite_generique'); $GLOBALS['spip_sqlite3_functions_1'] = _sqlite_ref_fonctions(); // http://code.spip.net/@req_sqlite3_dist function req_sqlite3_dist($addr, $port, $login, $pass, $db = '', $prefixe = '') { return req_sqlite_dist($addr, $port, $login, $pass, $db, $prefixe, $sqlite_version = 3); } // http://code.spip.net/@spip_sqlite3_constantes function spip_sqlite3_constantes() { if (!defined('SPIP_SQLITE3_ASSOC')) { define('SPIP_SQLITE3_ASSOC', PDO::FETCH_ASSOC); define('SPIP_SQLITE3_NUM', PDO::FETCH_NUM); define('SPIP_SQLITE3_BOTH', PDO::FETCH_BOTH); } } function spip_versions_sqlite3() { return _sqlite_charger_version(3) ? 3 : false; } sqlite2.php 0000666 00000002620 15245543621 0006653 0 ustar 00 <?php /***************************************************************************\ * SPIP, Systeme de publication pour l'internet * * * * Copyright (c) 2001-2019 * * Arnaud Martin, Antoine Pitrou, Philippe Riviere, Emmanuel Saint-James * * * * Ce programme est un logiciel libre distribue sous licence GNU/GPL. * * Pour plus de details voir le fichier COPYING.txt ou l'aide en ligne. * \***************************************************************************/ if (!defined('_ECRIRE_INC_VERSION')) { return; } include_spip('req/sqlite_generique'); $GLOBALS['spip_sqlite2_functions_1'] = _sqlite_ref_fonctions(); // http://code.spip.net/@req_sqlite2_dist function req_sqlite2_dist($addr, $port, $login, $pass, $db = '', $prefixe = '') { return req_sqlite_dist($addr, $port, $login, $pass, $db, $prefixe, $sqlite_version = 2); } // http://code.spip.net/@spip_sqlite2_constantes function spip_sqlite2_constantes() { if (!defined('SPIP_SQLITE2_ASSOC')) { define('SPIP_SQLITE2_ASSOC', SQLITE_ASSOC); define('SPIP_SQLITE2_NUM', SQLITE_NUM); define('SPIP_SQLITE2_BOTH', SQLITE_BOTH); } } function spip_versions_sqlite2() { return _sqlite_charger_version(2) ? 2 : false; } sqlite_fonctions.php 0000666 00000031377 15245543621 0010666 0 ustar 00 <?php /***************************************************************************\ * SPIP, Systeme de publication pour l'internet * * * * Copyright (c) 2001-2019 * * Arnaud Martin, Antoine Pitrou, Philippe Riviere, Emmanuel Saint-James * * * * Ce programme est un logiciel libre distribue sous licence GNU/GPL. * * Pour plus de details voir le fichier COPYING.txt ou l'aide en ligne. * \***************************************************************************/ /** * Ce fichier déclare des fonctions étendant les fonctions natives de SQLite * * @package SPIP\Core\SQL\SQLite\Fonctions */ if (!defined('_ECRIRE_INC_VERSION')) { return; } /** * Déclarer à SQLite des fonctions spécifiques utilisables dans les requêtes SQL * * SQLite ne supporte nativement que certaines fonctions dans les requêtes SQL. * Cependant, il permet d'étendre très facilement celles-ci en déclarant de * nouvelles fonctions. * * C'est ce qui est fait ici, en ajoutant des fonctions qui existent aussi * dans d'autres moteurs, notamment en MySQL. * * @link http://www.sqlite.org/lang_corefunc.html Liste des fonctions natives * @link http://sqlite.org/changes.html Liste des évolutions * * @param PDO|resource $sqlite Représente la connexion Sqlite */ function _sqlite_init_functions(&$sqlite) { if (!$sqlite) { return false; } $fonctions = array( // A 'ACOS' => array('acos', 1), 'ASIN' => array('asin', 1), 'ATAN' => array('atan', 1), // mysql accepte 2 params comme atan2… hum ? 'ATAN2' => array('atan2', 2), // C 'CEIL' => array('_sqlite_func_ceil', 1), 'CONCAT' => array('_sqlite_func_concat', -1), 'COS' => array('cos', 1), // D 'DATE_FORMAT' => array('_sqlite_func_strftime', 2), 'DAYOFMONTH' => array('_sqlite_func_dayofmonth', 1), 'DEGREES' => array('rad2deg', 1), // E 'EXTRAIRE_MULTI' => array('_sqlite_func_extraire_multi', 2), // specifique a SPIP/sql_multi() 'EXP' => array('exp', 1), // F 'FIND_IN_SET' => array('_sqlite_func_find_in_set', 2), 'FLOOR' => array('_sqlite_func_floor', 1), // I 'IF' => array('_sqlite_func_if', 3), 'INSERT' => array('_sqlite_func_insert', 4), 'INSTR' => array('_sqlite_func_instr', 2), // L 'LEAST' => array('_sqlite_func_least', 3), '_LEFT' => array('_sqlite_func_left', 2), # 'LENGTH' => array('strlen', 1), // present v1.0.4 # 'LOWER' => array('strtolower', 1), // present v2.4 # 'LTRIM' => array('ltrim', 1), // present // N 'NOW' => array('_sqlite_func_now', 0), // M 'MD5' => array('md5', 1), 'MONTH' => array('_sqlite_func_month', 1), // P 'PREG_REPLACE' => array('_sqlite_func_preg_replace', 3), // R 'RADIANS' => array('deg2rad', 1), 'RAND' => array('_sqlite_func_rand', 0), // sinon random() v2.4 'REGEXP' => array('_sqlite_func_regexp_match', 2), // critere REGEXP supporte a partir de v3.3.2 'RIGHT' => array('_sqlite_func_right', 2), # 'RTRIM' => array('rtrim', 1), // present // S 'SETTYPE' => array('settype', 2), // CAST present en v3.2.3 'SIN' => array('sin', 1), 'SQRT' => array('sqrt', 1), 'SUBSTRING' => array('_sqlite_func_substring' /*, 3*/), // peut etre appelee avec 2 ou 3 arguments, index base 1 et non 0 // T 'TAN' => array('tan', 1), 'TIMESTAMPDIFF' => array('_sqlite_timestampdiff' /*, 3*/), 'TO_DAYS' => array('_sqlite_func_to_days', 1), # 'TRIM' => array('trim', 1), // present // U 'UNIX_TIMESTAMP' => array('_sqlite_func_unix_timestamp', 1), # 'UPPER' => array('strtoupper', 1), // present v2.4 // V 'VIDE' => array('_sqlite_func_vide', 0), // du vide pour SELECT 0 as x ... ORDER BY x -> ORDER BY vide() // Y 'YEAR' => array('_sqlite_func_year', 1) ); foreach ($fonctions as $f => $r) { _sqlite_add_function($sqlite, $f, $r); } #spip_log('functions sqlite chargees ','sqlite.'._LOG_DEBUG); } /** * Déclare une fonction à SQLite * * @note * Permet au besoin de charger des fonctions * ailleurs par _sqlite_init_functions(); * * @uses _sqlite_is_version() * * @param PDO|resource $sqlite Représente la connexion Sqlite * @param string $f Nom de la fonction à créer * @param array $r Tableau indiquant : * - le nom de la fonction à appeler, * - le nombre de paramètres attendus de la fonction (-1 = infini, par défaut) * **/ function _sqlite_add_function(&$sqlite, &$f, &$r) { if (_sqlite_is_version(3, $sqlite)) { isset($r[1]) ? $sqlite->sqliteCreateFunction($f, $r[0], $r[1]) : $sqlite->sqliteCreateFunction($f, $r[0]); } else { isset($r[1]) ? sqlite_create_function($sqlite, $f, $r[0], $r[1]) : sqlite_create_function($sqlite, $f, $r[0]); } } // // SQLite : fonctions sqlite -> php // entre autre auteurs : mlebas // function _sqlite_func_ceil($a) { return ceil($a); } // http://code.spip.net/@_sqlite_func_concat function _sqlite_func_concat() { $args = func_get_args(); return join('', $args); } // http://code.spip.net/@_sqlite_func_dayofmonth function _sqlite_func_dayofmonth($d) { return _sqlite_func_date("d", $d); } // http://code.spip.net/@_sqlite_func_find_in_set function _sqlite_func_find_in_set($num, $set) { $rank = 0; foreach (explode(",", $set) as $v) { if ($v == $num) { return (++$rank); } $rank++; } return 0; } function _sqlite_func_floor($a) { return floor($a); } // http://code.spip.net/@_sqlite_func_if function _sqlite_func_if($bool, $oui, $non) { return ($bool) ? $oui : $non; } /* * INSERT(chaine, index, longueur, chaine) MySQL * Retourne une chaine de caracteres a partir d'une chaine dans laquelle "sschaine" * a ete inseree a la position "index" en remplacant "longueur" caracteres. */ // http://code.spip.net/@_sqlite_func_insert function _sqlite_func_insert($s, $index, $longueur, $chaine) { return substr($s, 0, $index) . $chaine . substr(substr($s, $index), $longueur); } // http://code.spip.net/@_sqlite_func_instr function _sqlite_func_instr($s, $search) { return strpos($s, $search); } // http://code.spip.net/@_sqlite_func_least function _sqlite_func_least() { $arg_list = func_get_args(); $least = min($arg_list); #spip_log("Passage avec LEAST : $least",'sqlite.'._LOG_DEBUG); return $least; } // http://code.spip.net/@_sqlite_func_left function _sqlite_func_left($s, $lenght) { return substr($s, $lenght); } // http://code.spip.net/@_sqlite_func_now function _sqlite_func_now() { static $now = null; if (is_null($now)) { $now = date("Y-m-d H:i:s"); } #spip_log("Passage avec NOW : $now",'sqlite.'._LOG_DEBUG); return $now; } // http://code.spip.net/@_sqlite_func_month function _sqlite_func_month($d) { return _sqlite_func_date("m", $d); } // http://code.spip.net/@_sqlite_func_preg_replace function _sqlite_func_preg_replace($quoi, $cherche, $remplace) { $return = preg_replace('%' . $cherche . '%', $remplace, $quoi); #spip_log("preg_replace : $quoi, $cherche, $remplace, $return",'sqlite.'._LOG_DEBUG); return $return; } /** * Extrait une langue d'un texte <multi>[fr] xxx [en] yyy</multi> * * @param string $quoi le texte contenant ou non un multi * @param string $lang la langue a extraire * @return string, l'extrait trouve. **/ function _sqlite_func_extraire_multi($quoi, $lang) { if (!defined('_EXTRAIRE_MULTI')) { include_spip('inc/filtres'); } if (!function_exists('approcher_langue')) { include_spip('inc/lang'); } if (preg_match_all(_EXTRAIRE_MULTI, $quoi, $regs, PREG_SET_ORDER)) { foreach ($regs as $reg) { // chercher la version de la langue courante $trads = extraire_trads($reg[1]); if ($l = approcher_langue($trads, $lang)) { $trad = $trads[$l]; } else { $trad = reset($trads); } $quoi = str_replace($reg[0], $trad, $quoi); } } return $quoi; } // http://code.spip.net/@_sqlite_func_rand function _sqlite_func_rand() { return rand(); } // http://code.spip.net/@_sqlite_func_right function _sqlite_func_right($s, $length) { return substr($s, 0 - $length); } // http://code.spip.net/@_sqlite_func_regexp_match function _sqlite_func_regexp_match($cherche, $quoi) { // optimiser un cas tres courant avec les requetes en base if (!$quoi and !strlen($quoi)) { return false; } $u = isset($GLOBALS['meta']['pcre_u']) ? $GLOBALS['meta']['pcre_u'] : 'u'; $return = preg_match('%' . $cherche . '%imsS' . $u, $quoi); #spip_log("regexp_replace : $quoi, $cherche, $remplace, $return",'sqlite.'._LOG_DEBUG); return $return; } // http://code.spip.net/@_sqlite_func_strftime function _sqlite_func_strftime($date, $conv) { return strftime($conv, is_int($date) ? $date : strtotime($date)); } /** * Nombre de jour entre 0000-00-00 et $d * * @link http://dev.mysql.com/doc/refman/5.5/en/date-and-time-functions.html#function_to-days * * @param string $d * @return int */ function _sqlite_func_to_days($d) { static $offset = 719528; // nb de jour entre 0000-00-00 et timestamp 0=1970-01-01 $result = $offset + (int)ceil(_sqlite_func_unix_timestamp($d) / (24 * 3600)); #spip_log("Passage avec TO_DAYS : $d, $result",'sqlite.'._LOG_DEBUG); return $result; } function _sqlite_func_substring($string, $start, $len = null) { // SQL compte a partir de 1, php a partir de 0 $start = ($start > 0) ? $start - 1 : $start; if (is_null($len)) { return substr($string, $start); } else { return substr($string, $start, $len); } } /** * Calcul de la difference entre 2 timestamp, exprimes dans l'unite fournie en premier argument * * @link https://dev.mysql.com/doc/refman/5.5/en/date-and-time-functions.html#function_timestampdiff * * @param string $unit * @param string $date1 * @param string $date2 * @return int */ function _sqlite_timestampdiff($unit, $date1, $date2) { // PHP >= 5.3 if (function_exists("date_diff")) { $d1 = date_create($date1); $d2 = date_create($date2); $diff = date_diff($d1, $d2); $inv = $diff->invert ? -1 : 1; switch ($unit) { case "YEAR": return $inv * $diff->y; case "QUARTER": return $inv * (4 * $diff->y + intval(floor($diff->m / 3))); case "MONTH": return $inv * (12 * $diff->y + $diff->m); case "WEEK": return $inv * intval(floor($diff->days / 7)); case "DAY": #var_dump($inv*$diff->days); return $inv * $diff->days; case "HOUR": return $inv * (24 * $diff->days + $diff->h); case "MINUTE": return $inv * ((24 * $diff->days + $diff->h) * 60 + $diff->i); case "SECOND": return $inv * (((24 * $diff->days + $diff->h) * 60 + $diff->i) * 60 + $diff->s); case "MICROSECOND": return $inv * (((24 * $diff->days + $diff->h) * 60 + $diff->i) * 60 + $diff->s) * 1000000; } return 0; } // PHP < 5.3 else { $d1 = strtotime($date1); $d2 = strtotime($date2); $diff = $d2 - $d1; $sign = ($diff < 0 ? -1 : 1); $diff = $sign * $diff; switch ($unit) { case "YEAR": $diff = $d2 - $d1; return $sign * (date('Y', abs($diff)) - date('Y', 0)); case "QUARTER": return $sign * (4 * (date('Y', abs($diff)) - date('Y', 0)) + intval(floor((date('m', $diff) - 1) / 3))); case "MONTH": return $sign * ((date('Y', $diff) - date('Y', 0)) * 12 + date('m', $diff) - 1); case "WEEK": return intval(floor(($d2 - $d1) / 3600 / 7)); case "DAY": return intval(floor(($d2 - $d1) / 3600 / 24)); case "HOUR": return intval(floor(($d2 - $d1) / 3600)); case "MINUTE": return intval(floor(($d2 - $d1) / 60)); case "SECOND": return $d2 - $d1; case "MICROSECOND": return $d2 - $d1 * 1000000; } } } // http://code.spip.net/@_sqlite_func_unix_timestamp function _sqlite_func_unix_timestamp($d) { static $mem = array(); static $n = 0; if (isset($mem[$d])) { return $mem[$d]; } if ($n++ > 100) { $mem = array(); $n = 0; } //2005-12-02 20:53:53 #spip_log("Passage avec UNIX_TIMESTAMP : $d",'sqlite.'._LOG_DEBUG); if (!$d) { return $mem[$d] = mktime(); } // une pile plus grosse n'accelere pas le calcul return $mem[$d] = strtotime($d); } // http://code.spip.net/@_sqlite_func_year function _sqlite_func_year($d) { return _sqlite_func_date("Y", $d); } /** * version optimisee et memoizee de date() utilisee par * _sqlite_func_year, _sqlite_func_month, _sqlite_func_dayofmonth * * @param string $quoi * format : Y, m, ou d * @param int $d * timestamp * @return int */ function _sqlite_func_date($quoi, $d) { static $mem = array(); static $n = 0; if (isset($mem[$d])) { return $mem[$d][$quoi]; } if ($n++ > 100) { $mem = array(); $n = 0; } $dec = date("Y-m-d", _sqlite_func_unix_timestamp($d)); $mem[$d] = array("Y" => substr($dec, 0, 4), "m" => substr($dec, 5, 2), "d" => substr($dec, 8, 2)); return $mem[$d][$quoi]; } // http://code.spip.net/@_sqlite_func_vide function _sqlite_func_vide() { return; } pg.exp.php 0000666 00000131301 15245543621 0006470 0 ustar 00 <?php /* *************************************************************************\ * SPIP, Systeme de publication pour l'internet * * * * Copyright (c) 2001-2019 * * Arnaud Martin, Antoine Pitrou, Philippe Riviere, Emmanuel Saint-James * * * * Ce programme est un logiciel libre distribue sous licence GNU/GPL. * * Pour plus de details voir le fichier COPYING.txt ou l'aide en ligne. * \***************************************************************************/ /** * Ce fichier contient les fonctions gerant * les instructions SQL pour PostgreSQL * * @package SPIP\Core\SQL\PostgreSQL */ if (!defined('_ECRIRE_INC_VERSION')) { return; } define('_DEFAULT_DB', 'spip'); // Se connecte et retourne le nom de la fonction a connexion persistante // A la premiere connexion de l'installation (BD pas precisee) // si on ne peut se connecter sans la preciser // on reessaye avec le login comme nom de BD // et si ca marche toujours pas, avec "spip" (constante ci-dessus) // si ca ne marche toujours pas, echec. // http://code.spip.net/@req_pg_dist function req_pg_dist($addr, $port, $login, $pass, $db = '', $prefixe = '') { static $last_connect = array(); if (!charger_php_extension('pgsql')) { return false; } // si provient de selectdb if (empty($addr) && empty($port) && empty($login) && empty($pass)) { foreach (array('addr', 'port', 'login', 'pass', 'prefixe') as $a) { $$a = $last_connect[$a]; } } @list($host, $p) = explode(';', $addr); if ($p > 0) { $port = " port=$p"; } else { $port = ''; } $erreurs = array(); if ($db) { @$link = pg_connect("host=$host$port dbname=$db user=$login password='$pass'", PGSQL_CONNECT_FORCE_NEW); } elseif (!@$link = pg_connect("host=$host$port user=$login password='$pass'", PGSQL_CONNECT_FORCE_NEW)) { $erreurs[] = pg_last_error(); if (@$link = pg_connect("host=$host$port dbname=$login user=$login password='$pass'", PGSQL_CONNECT_FORCE_NEW)) { $db = $login; } else { $erreurs[] = pg_last_error(); $db = _DEFAULT_DB; $link = pg_connect("host=$host$port dbname=$db user=$login password='$pass'", PGSQL_CONNECT_FORCE_NEW); } } if (!$link) { $erreurs[] = pg_last_error(); foreach ($erreurs as $e) { spip_log('Echec pg_connect. Erreur : ' . $e, 'pg.' . _LOG_HS); } return false; } if ($link) { $last_connect = array( 'addr' => $addr, 'port' => $port, 'login' => $login, 'pass' => $pass, 'db' => $db, 'prefixe' => $prefixe, ); } spip_log("Connexion vers $host, base $db, prefixe $prefixe " . ($link ? 'operationnelle' : 'impossible'), 'pg.' . _LOG_DEBUG); return !$link ? false : array( 'db' => $db, 'prefixe' => $prefixe ? $prefixe : $db, 'link' => $link, ); } $GLOBALS['spip_pg_functions_1'] = array( 'alter' => 'spip_pg_alter', 'count' => 'spip_pg_count', 'countsel' => 'spip_pg_countsel', 'create' => 'spip_pg_create', 'create_base' => 'spip_pg_create_base', 'create_view' => 'spip_pg_create_view', 'date_proche' => 'spip_pg_date_proche', 'delete' => 'spip_pg_delete', 'drop_table' => 'spip_pg_drop_table', 'drop_view' => 'spip_pg_drop_view', 'errno' => 'spip_pg_errno', 'error' => 'spip_pg_error', 'explain' => 'spip_pg_explain', 'fetch' => 'spip_pg_fetch', 'seek' => 'spip_pg_seek', 'free' => 'spip_pg_free', 'hex' => 'spip_pg_hex', 'in' => 'spip_pg_in', 'insert' => 'spip_pg_insert', 'insertq' => 'spip_pg_insertq', 'insertq_multi' => 'spip_pg_insertq_multi', 'listdbs' => 'spip_pg_listdbs', 'multi' => 'spip_pg_multi', 'optimize' => 'spip_pg_optimize', 'query' => 'spip_pg_query', 'quote' => 'spip_pg_quote', 'replace' => 'spip_pg_replace', 'replace_multi' => 'spip_pg_replace_multi', 'select' => 'spip_pg_select', 'selectdb' => 'spip_pg_selectdb', 'set_connect_charset' => 'spip_pg_set_connect_charset', 'showbase' => 'spip_pg_showbase', 'showtable' => 'spip_pg_showtable', 'update' => 'spip_pg_update', 'updateq' => 'spip_pg_updateq', ); // Par ou ca passe une fois les traductions faites // http://code.spip.net/@spip_pg_trace_query function spip_pg_trace_query($query, $serveur = '') { $connexion = &$GLOBALS['connexions'][$serveur ? strtolower($serveur) : 0]; $prefixe = $connexion['prefixe']; $link = $connexion['link']; $db = $connexion['db']; if (isset($_GET['var_profile'])) { include_spip('public/tracer'); $t = trace_query_start(); $e = ''; } else { $t = 0; } $connexion['last'] = $query; $r = spip_pg_query_simple($link, $query); // Log de l'erreur eventuelle if ($e = spip_pg_errno($serveur)) { $e .= spip_pg_error($query, $serveur); } // et du fautif return $t ? trace_query_end($query, $t, $r, $e, $serveur) : $r; } // Fonction de requete generale quand on est sur que c'est SQL standard. // Elle change juste le noms des tables ($table_prefix) dans le FROM etc // http://code.spip.net/@spip_pg_query function spip_pg_query($query, $serveur = '', $requeter = true) { $connexion = &$GLOBALS['connexions'][$serveur ? strtolower($serveur) : 0]; $prefixe = $connexion['prefixe']; $link = $connexion['link']; $db = $connexion['db']; if (preg_match('/\s(SET|VALUES|WHERE|DATABASE)\s/i', $query, $regs)) { $suite = strstr($query, $regs[0]); $query = substr($query, 0, -strlen($suite)); } else { $suite = ''; } $query = preg_replace('/([,\s])spip_/', '\1' . $prefixe . '_', $query) . $suite; // renvoyer la requete inerte si demandee if (!$requeter) { return $query; } return spip_pg_trace_query($query, $serveur); } function spip_pg_query_simple($link, $query) { #spip_log(var_export($query,true), 'pg.'._LOG_DEBUG); return pg_query($link, $query); } /* * Retrouver les champs 'timestamp' * pour les ajouter aux 'insert' ou 'replace' * afin de simuler le fonctionnement de mysql * * stocke le resultat pour ne pas faire * de requetes showtable intempestives */ function spip_pg_ajouter_champs_timestamp($table, $couples, $desc = '', $serveur = '') { static $tables = array(); if (!isset($tables[$table])) { if (!$desc) { $trouver_table = charger_fonction('trouver_table', 'base'); $desc = $trouver_table($table, $serveur); // si pas de description, on ne fait rien, ou on die() ? if (!$desc) { return $couples; } } // recherche des champs avec simplement 'TIMESTAMP' // cependant, il faudra peut etre etendre // avec la gestion de DEFAULT et ON UPDATE // mais ceux-ci ne sont pas utilises dans le core $tables[$table] = array(); foreach ($desc['field'] as $k => $v) { $v = strtolower(ltrim($v)); // ne pas ajouter de timestamp now() si un default est specifie if (strpos($v, 'timestamp') === 0 and strpos($v, 'default') === false) { $tables[$table][] = $k; } } } // ajout des champs type 'timestamp' absents foreach ($tables[$table] as $maj) { if (!array_key_exists($maj, $couples)) { $couples[$maj] = "NOW()"; } } return $couples; } // Alter en PG ne traite pas les index // http://code.spip.net/@spip_pg_alter function spip_pg_alter($query, $serveur = '', $requeter = true) { // il faudrait une regexp pour eviter de spliter ADD PRIMARY KEY (colA, colB) // tout en cassant en deux alter distincts "ADD PRIMARY KEY (colA, colB), ADD INDEX (chose)"... // ou revoir l'api de sql_alter en creant un // sql_alter_table($table,array($actions)); if (!preg_match("/\s*((\s*IGNORE)?\s*TABLE\s*([^\s]*))\s*(.*)?/is", $query, $regs)) { spip_log("$query mal comprise", 'pg.' . _LOG_ERREUR); return false; } $debut = $regs[1]; $table = $regs[3]; $suite = $regs[4]; $todo = explode(',', $suite); // on remet les morceaux dechires ensembles... que c'est laid ! $todo2 = array(); $i = 0; $ouverte = false; while ($do = array_shift($todo)) { $todo2[$i] = isset($todo2[$i]) ? $todo2[$i] . "," . $do : $do; $o = (false !== strpos($do, "(")); $f = (false !== strpos($do, ")")); if ($o and !$f) { $ouverte = true; } elseif ($f) { $ouverte = false; } if (!$ouverte) { $i++; } } $todo = $todo2; $query = $debut . ' ' . array_shift($todo); if (!preg_match('/^\s*(IGNORE\s*)?TABLE\s+(\w+)\s+(ADD|DROP|CHANGE|MODIFY|RENAME)\s*(.*)$/is', $query, $r)) { spip_log("$query incompris", 'pg.' . _LOG_ERREUR); } else { if ($r[1]) { spip_log("j'ignore IGNORE dans $query", 'pg.' . _LOG_AVERTISSEMENT); } $f = 'spip_pg_alter_' . strtolower($r[3]); if (function_exists($f)) { $f($r[2], $r[4], $serveur, $requeter); } else { spip_log("$query non prevu", 'pg.' . _LOG_ERREUR); } } // Alter a plusieurs args. Faudrait optimiser. if ($todo) { spip_pg_alter("TABLE $table " . join(',', $todo)); } } // http://code.spip.net/@spip_pg_alter_change function spip_pg_alter_change($table, $arg, $serveur = '', $requeter = true) { if (!preg_match('/^`?(\w+)`?\s+`?(\w+)`?\s+(.*?)\s*(DEFAULT .*?)?(NOT\s+NULL)?\s*(DEFAULT .*?)?$/i', $arg, $r)) { spip_log("alter change: $arg incompris", 'pg.' . _LOG_ERREUR); } else { list(, $old, $new, $type, $default, $null, $def2) = $r; $actions = array("ALTER $old TYPE " . mysql2pg_type($type)); if ($null) { $actions[] = "ALTER $old SET NOT NULL"; } else { $actions[] = "ALTER $old DROP NOT NULL"; } if ($d = ($default ? $default : $def2)) { $actions[] = "ALTER $old SET $d"; } else { $actions[] = "ALTER $old DROP DEFAULT"; } spip_pg_query("ALTER TABLE $table " . join(', ', $actions)); if ($old != $new) { spip_pg_query("ALTER TABLE $table RENAME $old TO $new", $serveur); } } } // http://code.spip.net/@spip_pg_alter_add function spip_pg_alter_add($table, $arg, $serveur = '', $requeter = true) { if (!preg_match('/^(COLUMN|INDEX|KEY|PRIMARY\s+KEY|)\s*(.*)$/', $arg, $r)) { spip_log("alter add $arg incompris", 'pg.' . _LOG_ERREUR); return null; } if (!$r[1] or $r[1] == 'COLUMN') { preg_match('/`?(\w+)`?(.*)/', $r[2], $m); if (preg_match('/^(.*)(BEFORE|AFTER|FIRST)(.*)$/is', $m[2], $n)) { $m[2] = $n[1]; } return spip_pg_query("ALTER TABLE $table ADD " . $m[1] . ' ' . mysql2pg_type($m[2]), $serveur, $requeter); } elseif ($r[1][0] == 'P') { // la primary peut etre sur plusieurs champs $r[2] = trim(str_replace('`', '', $r[2])); $m = ($r[2][0] == '(') ? substr($r[2], 1, -1) : $r[2]; return spip_pg_query("ALTER TABLE $table ADD CONSTRAINT $table" . '_pkey PRIMARY KEY (' . $m . ')', $serveur, $requeter); } else { preg_match('/([^\s,]*)\s*(.*)?/', $r[2], $m); // peut etre "(colonne)" ou "nom_index (colonnes)" // bug potentiel si qqn met "(colonne, colonne)" // // nom_index (colonnes) if ($m[2]) { $colonnes = substr($m[2], 1, -1); $nom_index = $m[1]; } else { // (colonne) if ($m[1][0] == "(") { $colonnes = substr($m[1], 1, -1); if (false !== strpos(",", $colonnes)) { spip_log(_LOG_GRAVITE_ERREUR, "PG : Erreur, impossible de creer un index sur plusieurs colonnes" . " sans qu'il ait de nom ($table, ($colonnes))", 'pg'); } else { $nom_index = $colonnes; } } // nom_index else { $nom_index = $colonnes = $m[1]; } } return spip_pg_create_index($nom_index, $table, $colonnes, $serveur, $requeter); } } // http://code.spip.net/@spip_pg_alter_drop function spip_pg_alter_drop($table, $arg, $serveur = '', $requeter = true) { if (!preg_match('/^(COLUMN|INDEX|KEY|PRIMARY\s+KEY|)\s*`?(\w*)`?/', $arg, $r)) { spip_log("alter drop: $arg incompris", 'pg.' . _LOG_ERREUR); } else { if (!$r[1] or $r[1] == 'COLUMN') { return spip_pg_query("ALTER TABLE $table DROP " . $r[2], $serveur); } elseif ($r[1][0] == 'P') { return spip_pg_query("ALTER TABLE $table DROP CONSTRAINT $table" . '_pkey', $serveur); } else { return spip_pg_query("DROP INDEX " . $table . '_' . $r[2], $serveur); } } } function spip_pg_alter_modify($table, $arg, $serveur = '', $requeter = true) { if (!preg_match('/^`?(\w+)`?\s+(.*)$/', $arg, $r)) { spip_log("alter modify: $arg incompris", 'pg.' . _LOG_ERREUR); } else { return spip_pg_alter_change($table, $r[1] . ' ' . $arg, $serveur = '', $requeter = true); } } // attention (en pg) : // - alter table A rename to X = changer le nom de la table // - alter table A rename X to Y = changer le nom de la colonne X en Y // pour l'instant, traiter simplement RENAME TO X function spip_pg_alter_rename($table, $arg, $serveur = '', $requeter = true) { $rename = ""; // si TO, mais pas au debut if (!stripos($arg, 'TO ')) { $rename = $arg; } elseif (preg_match('/^(TO)\s*`?(\w*)`?/', $arg, $r)) { $rename = $r[2]; } else { spip_log("alter rename: $arg incompris", 'pg.' . _LOG_ERREUR); } return $rename ? spip_pg_query("ALTER TABLE $table RENAME TO $rename") : false; } /** * Fonction de creation d'un INDEX * * @param string $nom : nom de l'index * @param string $table : table sql de l'index * @param string /array $champs : liste de champs sur lesquels s'applique l'index * @param string $serveur : nom de la connexion sql utilisee * @param bool $requeter : true pour executer la requete ou false pour retourner le texte de la requete * * @return bool ou requete */ function spip_pg_create_index($nom, $table, $champs, $serveur = '', $requeter = true) { if (!($nom or $table or $champs)) { spip_log("Champ manquant pour creer un index pg ($nom, $table, (" . @join(',', $champs) . "))", 'pg.' . _LOG_ERREUR); return false; } $nom = str_replace("`", "", $nom); $champs = str_replace("`", "", $champs); // PG ne differentie pas noms des index en fonction des tables // il faut donc creer des noms uniques d'index pour une base pg $nom = $table . '_' . $nom; // enlever d'eventuelles parentheses deja presentes sur champs if (!is_array($champs)) { if ($champs[0] == "(") { $champs = substr($champs, 1, -1); } $champs = array($champs); } $query = "CREATE INDEX $nom ON $table (" . join(',', $champs) . ")"; if (!$requeter) { return $query; } $res = spip_pg_query($query, $serveur, $requeter); return $res; } // http://code.spip.net/@spip_pg_explain function spip_pg_explain($query, $serveur = '', $requeter = true) { if (strpos(ltrim($query), 'SELECT') !== 0) { return array(); } $connexion = &$GLOBALS['connexions'][$serveur ? strtolower($serveur) : 0]; $prefixe = $connexion['prefixe']; $link = $connexion['link']; if (preg_match('/\s(SET|VALUES|WHERE)\s/i', $query, $regs)) { $suite = strstr($query, $regs[0]); $query = substr($query, 0, -strlen($suite)); } else { $suite = ''; } $query = 'EXPLAIN ' . preg_replace('/([,\s])spip_/', '\1' . $prefixe . '_', $query) . $suite; if (!$requeter) { return $query; } $r = spip_pg_query_simple($link, $query); return spip_pg_fetch($r, null, $serveur); } /** * Sélectionne une base de données * * @param string $db * Nom de la base à utiliser * @param string $serveur * Nom du connecteur * @param bool $requeter * Inutilisé * * @return bool|string * - Nom de la base en cas de succès. * - False en cas d'erreur. **/ function spip_pg_selectdb($db, $serveur = '', $requeter = true) { // se connecter a la base indiquee // avec les identifiants connus $index = $serveur ? strtolower($serveur) : 0; if ($link = spip_connect_db('', '', '', '', $db, 'pg', '', '')) { if (($db == $link['db']) && $GLOBALS['connexions'][$index] = $link) { return $db; } } else { return false; } } // Qu'une seule base pour le moment // http://code.spip.net/@spip_pg_listdbs function spip_pg_listdbs($serveur) { $connexion = &$GLOBALS['connexions'][$serveur ? strtolower($serveur) : 0]; $link = $connexion['link']; $dbs = array(); $res = spip_pg_query_simple($link, "select * From pg_database"); while ($row = pg_fetch_array($res, null, PGSQL_NUM)) { $dbs[] = reset($row); } return $dbs; } // http://code.spip.net/@spip_pg_select function spip_pg_select( $select, $from, $where = '', $groupby = array(), $orderby = '', $limit = '', $having = '', $serveur = '', $requeter = true ) { $connexion = &$GLOBALS['connexions'][$serveur ? strtolower($serveur) : 0]; $prefixe = $connexion['prefixe']; $link = $connexion['link']; $db = $connexion['db']; $limit = preg_match("/^\s*(([0-9]+),)?\s*([0-9]+)\s*$/", $limit, $limatch); if ($limit) { $offset = $limatch[2]; $count = $limatch[3]; } $select = spip_pg_frommysql($select); // si pas de tri explicitement demande, le GROUP BY ne // contient que la clef primaire. // lui ajouter alors le champ de tri par defaut if (preg_match("/FIELD\(([a-z]+\.[a-z]+),/i", $orderby[0], $groupbyplus)) { $groupby[] = $groupbyplus[1]; } $orderby = spip_pg_orderby($orderby, $select); if ($having) { if (is_array($having)) { $having = join("\n\tAND ", array_map('calculer_pg_where', $having)); } } $from = spip_pg_from($from, $prefixe); $query = "SELECT " . $select . (!$from ? '' : "\nFROM $from") . (!$where ? '' : ("\nWHERE " . (!is_array($where) ? calculer_pg_where($where) : (join("\n\tAND ", array_map('calculer_pg_where', $where)))))) . spip_pg_groupby($groupby, $from, $select) . (!$having ? '' : "\nHAVING $having") . ($orderby ? ("\nORDER BY $orderby") : '') . (!$limit ? '' : (" LIMIT $count" . (!$offset ? '' : " OFFSET $offset"))); // renvoyer la requete inerte si demandee if ($requeter === false) { return $query; } $r = spip_pg_trace_query($query, $serveur); return $r ? $r : $query;; } // Le traitement des prefixes de table dans un Select se limite au FROM // car le reste de la requete utilise les alias (AS) systematiquement // http://code.spip.net/@spip_pg_from function spip_pg_from($from, $prefixe) { if (is_array($from)) { $from = spip_pg_select_as($from); } return !$prefixe ? $from : preg_replace('/(\b)spip_/', '\1' . $prefixe . '_', $from); } // http://code.spip.net/@spip_pg_orderby function spip_pg_orderby($order, $select) { $res = array(); $arg = (is_array($order) ? $order : preg_split('/\s*,\s*/', $order)); foreach ($arg as $v) { if (preg_match('/(case\s+.*?else\s+0\s+end)\s*AS\s+' . $v . '/', $select, $m)) { $res[] = $m[1]; } else { $res[] = $v; } } return spip_pg_frommysql(join(',', $res)); } // Conversion a l'arrach' des jointures MySQL en jointures PG // A refaire pour tirer parti des possibilites de PG et de MySQL5 // et pour enlever les repetitions (sans incidence de perf, mais ca fait sale) // http://code.spip.net/@spip_pg_groupby function spip_pg_groupby($groupby, $from, $select) { $join = strpos($from, ","); // ismplifier avant de decouper if (is_string($select)) // fct SQL sur colonne et constante apostrophee ==> la colonne { $select = preg_replace('/\w+\(\s*([^(),\']*),\s*\'[^\']*\'[^)]*\)/', '\\1', $select); } if ($join or $groupby) { $join = is_array($select) ? $select : explode(", ", $select); } if ($join) { // enlever les 0 as points, '', ... foreach ($join as $k => $v) { $v = str_replace('DISTINCT ', '', $v); // fct SQL sur colonne et constante apostrophee ==> la colonne $v = preg_replace('/\w+\(\s*([^(),\']*),\s*\'[^\']*\'[^)]*\)/', '\\1', $v); $v = preg_replace('/CAST\(\s*([^(),\' ]*\s+)as\s*\w+\)/', '\\1', $v); // resultat d'agregat ne sont pas a mettre dans le groupby $v = preg_replace('/(SUM|COUNT|MAX|MIN|UPPER)\([^)]+\)(\s*AS\s+\w+)\s*,?/i', '', $v); // idem sans AS (fetch numerique) $v = preg_replace('/(SUM|COUNT|MAX|MIN|UPPER)\([^)]+\)\s*,?/i', '', $v); // des AS simples : on garde le cote droit du AS $v = preg_replace('/^.*\sAS\s+(\w+)\s*$/i', '\\1', $v); // ne reste plus que les vrais colonnes, ou des constantes a virer if (preg_match(',^[\'"],', $v) or is_numeric($v)) { unset($join[$k]); } else { $join[$k] = trim($v); } } $join = array_diff($join, array('')); $join = implode(',', $join); } if (is_array($groupby)) { $groupby = join(',', $groupby); } if ($join) { $groupby = $groupby ? "$groupby, $join" : $join; } if (!$groupby) { return ''; } $groupby = spip_pg_frommysql($groupby); // Ne pas mettre dans le Group-By des valeurs numeriques // issue de prepare_recherche $groupby = preg_replace('/^\s*\d+\s+AS\s+\w+\s*,?\s*/i', '', $groupby); $groupby = preg_replace('/,\s*\d+\s+AS\s+\w+\s*/i', '', $groupby); $groupby = preg_replace('/\s+AS\s+\w+\s*/i', '', $groupby); return "\nGROUP BY $groupby"; } // Conversion des operateurs MySQL en PG // IMPORTANT: "0+X" est vu comme conversion numerique du debut de X // Les expressions de date ne sont pas gerees au-dela de 3 () // Le 'as' du 'CAST' est en minuscule pour echapper au dernier preg_replace // de spip_pg_groupby. // A ameliorer. // http://code.spip.net/@spip_pg_frommysql function spip_pg_frommysql($arg) { if (is_array($arg)) { $arg = join(", ", $arg); } $res = spip_pg_fromfield($arg); $res = preg_replace('/\brand[(][)]/i', 'random()', $res); $res = preg_replace('/\b0\.0[+]([a-zA-Z0-9_.]+)\s*/', 'CAST(substring(\1, \'^ *[0-9.]+\') as float)', $res); $res = preg_replace('/\b0[+]([a-zA-Z0-9_.]+)\s*/', 'CAST(substring(\1, \'^ *[0-9]+\') as int)', $res); $res = preg_replace('/\bconv[(]([^,]*)[^)]*[)]/i', 'CAST(substring(\1, \'^ *[0-9]+\') as int)', $res); $res = preg_replace('/UNIX_TIMESTAMP\s*[(]\s*[)]/', ' EXTRACT(epoch FROM NOW())', $res); // la fonction md5(integer) n'est pas connu en pg // il faut donc forcer les types en text (cas de md5(id_article)) $res = preg_replace('/md5\s*[(]([^)]*)[)]/i', 'MD5(CAST(\1 AS text))', $res); $res = preg_replace('/UNIX_TIMESTAMP\s*[(]([^)]*)[)]/', ' EXTRACT(epoch FROM \1)', $res); $res = preg_replace('/\bDAYOFMONTH\s*[(]([^()]*([(][^()]*[)][^()]*)*[^)]*)[)]/', ' EXTRACT(day FROM \1)', $res); $res = preg_replace('/\bMONTH\s*[(]([^()]*([(][^)]*[)][^()]*)*[^)]*)[)]/', ' EXTRACT(month FROM \1)', $res); $res = preg_replace('/\bYEAR\s*[(]([^()]*([(][^)]*[)][^()]*)*[^)]*)[)]/', ' EXTRACT(year FROM \1)', $res); $res = preg_replace('/TO_DAYS\s*[(]([^()]*([(][^)]*[)][()]*)*)[)]/', ' EXTRACT(day FROM \1 - \'0001-01-01\')', $res); $res = preg_replace("/(EXTRACT[(][^ ]* FROM *)\"([^\"]*)\"/", '\1\'\2\'', $res); $res = preg_replace('/DATE_FORMAT\s*[(]([^,]*),\s*\'%Y%m%d\'[)]/', 'to_char(\1, \'YYYYMMDD\')', $res); $res = preg_replace('/DATE_FORMAT\s*[(]([^,]*),\s*\'%Y%m\'[)]/', 'to_char(\1, \'YYYYMM\')', $res); $res = preg_replace('/DATE_SUB\s*[(]([^,]*),/', '(\1 -', $res); $res = preg_replace('/DATE_ADD\s*[(]([^,]*),/', '(\1 +', $res); $res = preg_replace('/INTERVAL\s+(\d+\s+\w+)/', 'INTERVAL \'\1\'', $res); $res = preg_replace('/([+<>-]=?)\s*(\'\d+-\d+-\d+\s+\d+:\d+(:\d+)\')/', '\1 timestamp \2', $res); $res = preg_replace('/(\'\d+-\d+-\d+\s+\d+:\d+:\d+\')\s*([+<>-]=?)/', 'timestamp \1 \2', $res); $res = preg_replace('/([+<>-]=?)\s*(\'\d+-\d+-\d+\')/', '\1 timestamp \2', $res); $res = preg_replace('/(\'\d+-\d+-\d+\')\s*([+<>-]=?)/', 'timestamp \1 \2', $res); $res = preg_replace('/(timestamp .\d+)-00-/', '\1-01-', $res); $res = preg_replace('/(timestamp .\d+-\d+)-00/', '\1-01', $res); # correct en theorie mais produit des debordements arithmetiques # $res = preg_replace("/(EXTRACT[(][^ ]* FROM *)(timestamp *'[^']*' *[+-] *timestamp *'[^']*') *[)]/", '\2', $res); $res = preg_replace("/(EXTRACT[(][^ ]* FROM *)('[^']*')/", '\1 timestamp \2', $res); $res = preg_replace("/\sLIKE\s+/", ' ILIKE ', $res); return str_replace('REGEXP', '~', $res); } // http://code.spip.net/@spip_pg_fromfield function spip_pg_fromfield($arg) { while (preg_match('/^(.*?)FIELD\s*\(([^,]*)((,[^,)]*)*)\)/', $arg, $m)) { preg_match_all('/,([^,]*)/', $m[3], $r, PREG_PATTERN_ORDER); $res = ''; $n = 0; $index = $m[2]; foreach ($r[1] as $v) { $n++; $res .= "\nwhen $index=$v then $n"; } $arg = $m[1] . "case $res else 0 end " . substr($arg, strlen($m[0])); } return $arg; } // http://code.spip.net/@calculer_pg_where function calculer_pg_where($v) { if (!is_array($v)) { return spip_pg_frommysql($v); } $op = str_replace('REGEXP', '~', array_shift($v)); if (!($n = count($v))) { return $op; } else { $arg = calculer_pg_where(array_shift($v)); if ($n == 1) { return "$op($arg)"; } else { $arg2 = calculer_pg_where(array_shift($v)); if ($n == 2) { return "($arg $op $arg2)"; } else { return "($arg $op ($arg2) : $v[0])"; } } } } // http://code.spip.net/@calculer_pg_expression function calculer_pg_expression($expression, $v, $join = 'AND') { if (empty($v)) { return ''; } $exp = "\n$expression "; if (!is_array($v)) { $v = array($v); } if (strtoupper($join) === 'AND') { return $exp . join("\n\t$join ", array_map('calculer_pg_where', $v)); } else { return $exp . join($join, $v); } } // http://code.spip.net/@spip_pg_select_as function spip_pg_select_as($args) { $argsas = ""; foreach ($args as $k => $v) { if (substr($k, -1) == '@') { // c'est une jointure qui se refere au from precedent // pas de virgule $argsas .= ' ' . $v; } else { $as = ''; // spip_log("$k : $v", _LOG_DEBUG); if (!is_numeric($k)) { if (preg_match('/\.(.*)$/', $k, $r)) { $v = $k; } elseif ($v != $k) { $p = strpos($v, " "); if ($p) { $v = substr($v, 0, $p) . " AS $k" . substr($v, $p); } else { $as = " AS $k"; } } } // spip_log("subs $k : $v avec $as", _LOG_DEBUG); // if (strpos($v, 'JOIN') === false) $argsas .= ', '; $argsas .= ', ' . $v . $as; } } return substr($argsas, 2); } // http://code.spip.net/@spip_pg_fetch function spip_pg_fetch($res, $t = '', $serveur = '', $requeter = true) { if ($res) { $res = pg_fetch_array($res, null, PGSQL_ASSOC); } return $res; } function spip_pg_seek($r, $row_number, $serveur = '', $requeter = true) { if ($r) { return pg_result_seek($r, $row_number); } } // http://code.spip.net/@spip_pg_countsel function spip_pg_countsel( $from = array(), $where = array(), $groupby = array(), $having = array(), $serveur = '', $requeter = true ) { $c = !$groupby ? '*' : ('DISTINCT ' . (is_string($groupby) ? $groupby : join(',', $groupby))); $r = spip_pg_select("COUNT($c)", $from, $where, '', '', '', $having, $serveur, $requeter); if (!$requeter) { return $r; } if (!is_resource($r)) { return 0; } list($c) = pg_fetch_array($r, null, PGSQL_NUM); return $c; } // http://code.spip.net/@spip_pg_count function spip_pg_count($res, $serveur = '', $requeter = true) { return !$res ? 0 : pg_numrows($res); } // http://code.spip.net/@spip_pg_free function spip_pg_free($res, $serveur = '', $requeter = true) { // rien a faire en postgres } // http://code.spip.net/@spip_pg_delete function spip_pg_delete($table, $where = '', $serveur = '', $requeter = true) { $connexion = &$GLOBALS['connexions'][$serveur ? strtolower($serveur) : 0]; $table = prefixer_table_spip($table, $connexion['prefixe']); $query = calculer_pg_expression('DELETE FROM', $table, ',') . calculer_pg_expression('WHERE', $where, 'AND'); // renvoyer la requete inerte si demandee if (!$requeter) { return $query; } $res = spip_pg_trace_query($query, $serveur); if ($res) { return pg_affected_rows($res); } else { return false; } } // http://code.spip.net/@spip_pg_insert function spip_pg_insert($table, $champs, $valeurs, $desc = array(), $serveur = '', $requeter = true) { $connexion = &$GLOBALS['connexions'][$serveur ? strtolower($serveur) : 0]; $prefixe = $connexion['prefixe']; $link = $connexion['link']; if (!$desc) { $desc = description_table($table, $serveur); } $seq = spip_pg_sequence($table, true); // si pas de cle primaire dans l'insertion, renvoyer curval if (!preg_match(",\b$seq\b,", $champs)) { $seq = spip_pg_sequence($table); $seq = prefixer_table_spip($seq, $prefixe); $seq = "currval('$seq')"; } $table = prefixer_table_spip($table, $prefixe); $ret = !$seq ? '' : (" RETURNING $seq"); $ins = (strlen($champs) < 3) ? " DEFAULT VALUES" : "$champs VALUES $valeurs"; $q = "INSERT INTO $table $ins $ret"; if (!$requeter) { return $q; } $connexion['last'] = $q; $r = spip_pg_query_simple($link, $q); # spip_log($q,'pg.'._LOG_DEBUG); if ($r) { if (!$ret) { return 0; } if ($r2 = pg_fetch_array($r, null, PGSQL_NUM)) { return $r2[0]; } } return false; } // http://code.spip.net/@spip_pg_insertq function spip_pg_insertq($table, $couples = array(), $desc = array(), $serveur = '', $requeter = true) { if (!$desc) { $desc = description_table($table, $serveur); } if (!$desc) { die("$table insertion sans description"); } $fields = $desc['field']; foreach ($couples as $champ => $val) { $couples[$champ] = spip_pg_cite($val, $fields[$champ]); } // recherche de champs 'timestamp' pour mise a jour auto de ceux-ci $couples = spip_pg_ajouter_champs_timestamp($table, $couples, $desc, $serveur); return spip_pg_insert($table, "(" . join(',', array_keys($couples)) . ")", "(" . join(',', $couples) . ")", $desc, $serveur, $requeter); } // http://code.spip.net/@spip_pg_insertq_multi function spip_pg_insertq_multi($table, $tab_couples = array(), $desc = array(), $serveur = '', $requeter = true) { if (!$desc) { $desc = description_table($table, $serveur); } if (!$desc) { die("$table insertion sans description"); } $fields = isset($desc['field']) ? $desc['field'] : array(); // recherche de champs 'timestamp' pour mise a jour auto de ceux-ci // une premiere fois pour ajouter maj dans les cles $c = isset($tab_couples[0]) ? $tab_couples[0] : array(); $les_cles = spip_pg_ajouter_champs_timestamp($table, $c, $desc, $serveur); $cles = "(" . join(',', array_keys($les_cles)) . ')'; $valeurs = array(); foreach ($tab_couples as $couples) { foreach ($couples as $champ => $val) { $couples[$champ] = spip_pg_cite($val, $fields[$champ]); } // recherche de champs 'timestamp' pour mise a jour auto de ceux-ci $couples = spip_pg_ajouter_champs_timestamp($table, $couples, $desc, $serveur); $valeurs[] = '(' . join(',', $couples) . ')'; } $valeurs = implode(', ', $valeurs); return spip_pg_insert($table, $cles, $valeurs, $desc, $serveur, $requeter); } // http://code.spip.net/@spip_pg_update function spip_pg_update($table, $couples, $where = '', $desc = '', $serveur = '', $requeter = true) { if (!$couples) { return; } $connexion = $GLOBALS['connexions'][$serveur ? strtolower($serveur) : 0]; $table = prefixer_table_spip($table, $connexion['prefixe']); // recherche de champs 'timestamp' pour mise a jour auto de ceux-ci $couples = spip_pg_ajouter_champs_timestamp($table, $couples, $desc, $serveur); $set = array(); foreach ($couples as $champ => $val) { $set[] = $champ . '=' . $val; } $query = calculer_pg_expression('UPDATE', $table, ',') . calculer_pg_expression('SET', $set, ',') . calculer_pg_expression('WHERE', $where, 'AND'); // renvoyer la requete inerte si demandee if (!$requeter) { return $query; } return spip_pg_trace_query($query, $serveur); } // idem, mais les valeurs sont des constantes a mettre entre apostrophes // sauf les expressions de date lorsqu'il s'agit de fonctions SQL (NOW etc) // http://code.spip.net/@spip_pg_updateq function spip_pg_updateq($table, $couples, $where = '', $desc = array(), $serveur = '', $requeter = true) { if (!$couples) { return; } if (!$desc) { $desc = description_table($table, $serveur); } $fields = $desc['field']; foreach ($couples as $k => $val) { $couples[$k] = spip_pg_cite($val, $fields[$k]); } return spip_pg_update($table, $couples, $where, $desc, $serveur, $requeter); } // http://code.spip.net/@spip_pg_replace function spip_pg_replace($table, $values, $desc, $serveur = '', $requeter = true) { if (!$values) { spip_log("replace vide $table", 'pg.' . _LOG_AVERTISSEMENT); return 0; } $connexion = &$GLOBALS['connexions'][$serveur ? strtolower($serveur) : 0]; $prefixe = $connexion['prefixe']; $link = $connexion['link']; if (!$desc) { $desc = description_table($table, $serveur); } if (!$desc) { die("$table insertion sans description"); } $prim = $desc['key']['PRIMARY KEY']; $ids = preg_split('/,\s*/', $prim); $noprims = $prims = array(); foreach ($values as $k => $v) { $values[$k] = $v = spip_pg_cite($v, $desc['field'][$k]); if (!in_array($k, $ids)) { $noprims[$k] = "$k=$v"; } else { $prims[$k] = "$k=$v"; } } // recherche de champs 'timestamp' pour mise a jour auto de ceux-ci $values = spip_pg_ajouter_champs_timestamp($table, $values, $desc, $serveur); $where = join(' AND ', $prims); if (!$where) { return spip_pg_insert($table, "(" . join(',', array_keys($values)) . ")", "(" . join(',', $values) . ")", $desc, $serveur); } $couples = join(',', $noprims); $seq = spip_pg_sequence($table); $table = prefixer_table_spip($table, $prefixe); $seq = prefixer_table_spip($seq, $prefixe); $connexion['last'] = $q = "UPDATE $table SET $couples WHERE $where"; if ($couples) { $couples = spip_pg_query_simple($link, $q); # spip_log($q,'pg.'._LOG_DEBUG); if (!$couples) { return false; } $couples = pg_affected_rows($couples); } if (!$couples) { $ret = !$seq ? '' : (" RETURNING nextval('$seq') < $prim"); $connexion['last'] = $q = "INSERT INTO $table (" . join(',', array_keys($values)) . ') VALUES (' . join(',', $values) . ")$ret"; $couples = spip_pg_query_simple($link, $q); if (!$couples) { return false; } elseif ($ret) { $r = pg_fetch_array($couples, null, PGSQL_NUM); if ($r[0]) { $connexion['last'] = $q = "SELECT setval('$seq', $prim) from $table"; // Le code de SPIP met parfois la sequence a 0 (dans l'import) // MySQL n'en dit rien, on fait pareil pour PG $r = @pg_query($link, $q); } } } return $couples; } // http://code.spip.net/@spip_pg_replace_multi function spip_pg_replace_multi($table, $tab_couples, $desc = array(), $serveur = '', $requeter = true) { // boucler pour traiter chaque requete independemment foreach ($tab_couples as $couples) { $retour = spip_pg_replace($table, $couples, $desc, $serveur, $requeter); } // renvoie le dernier id return $retour; } // Donne la sequence eventuelle associee a une table // Pas extensible pour le moment, // http://code.spip.net/@spip_pg_sequence function spip_pg_sequence($table, $raw = false) { include_spip('base/serial'); if (!isset($GLOBALS['tables_principales'][$table])) { return false; } $desc = $GLOBALS['tables_principales'][$table]; $prim = @$desc['key']['PRIMARY KEY']; if (!preg_match('/^\w+$/', $prim) or strpos($desc['field'][$prim], 'int') === false ) { return ''; } else { return $raw ? $prim : $table . '_' . $prim . "_seq"; } } // Explicite les conversions de Mysql d'une valeur $v de type $t // Dans le cas d'un champ date, pas d'apostrophe, c'est une syntaxe ad hoc // http://code.spip.net/@spip_pg_cite function spip_pg_cite($v, $t) { if (is_null($v)) { return 'NULL'; } // null php se traduit en NULL SQL if (sql_test_date($t)) { if ($v and (strpos("0123456789", $v[0]) === false)) { return spip_pg_frommysql($v); } else { if (strncmp($v, '0000', 4) == 0) { $v = "0001" . substr($v, 4); } if (strpos($v, "-00-00") === 4) { $v = substr($v, 0, 4) . "-01-01" . substr($v, 10); } return "timestamp '$v'"; } } elseif (!sql_test_int($t)) { return ("'" . pg_escape_string($v) . "'"); } elseif (is_numeric($v) or (strpos($v, 'CAST(') === 0)) { return $v; } elseif ($v[0] == '0' and $v[1] !== 'x' and ctype_xdigit(substr($v, 1))) { return substr($v, 1); } else { spip_log("Warning: '$v' n'est pas de type $t", 'pg.' . _LOG_AVERTISSEMENT); return intval($v); } } // http://code.spip.net/@spip_pg_hex function spip_pg_hex($v) { return "CAST(x'" . $v . "' as bigint)"; } function spip_pg_quote($v, $type = '') { if (!is_array($v)) { return spip_pg_cite($v, $type); } // si c'est un tableau, le parcourir en propageant le type foreach ($v as $k => $r) { $v[$k] = spip_pg_quote($r, $type); } return join(",", $v); } function spip_pg_date_proche($champ, $interval, $unite) { return '(' . $champ . (($interval <= 0) ? '>' : '<') . (($interval <= 0) ? 'DATE_SUB' : 'DATE_ADD') . '(' . sql_quote(date('Y-m-d H:i:s')) . ', INTERVAL ' . (($interval > 0) ? $interval : (0 - $interval)) . ' ' . $unite . '))'; } // http://code.spip.net/@spip_pg_in function spip_pg_in($val, $valeurs, $not = '', $serveur) { // // IN (...) souvent limite a 255 elements, d'ou cette fonction assistante // // s'il n'y a pas de valeur, eviter de produire un IN vide: PG rale. if (!$valeurs) { return $not ? '0=0' : '0=1'; } if (strpos($valeurs, "CAST(x'") !== false) { return "($val=" . join("OR $val=", explode(',', $valeurs)) . ')'; } $n = $i = 0; $in_sql = ""; while ($n = strpos($valeurs, ',', $n + 1)) { if ((++$i) >= 255) { $in_sql .= "($val $not IN (" . substr($valeurs, 0, $n) . "))\n" . ($not ? "AND\t" : "OR\t"); $valeurs = substr($valeurs, $n + 1); $i = $n = 0; } } $in_sql .= "($val $not IN ($valeurs))"; return "($in_sql)"; } // http://code.spip.net/@spip_pg_error function spip_pg_error($query = '', $serveur, $requeter = true) { $link = $GLOBALS['connexions'][$serveur ? strtolower($serveur) : 0]['link']; $s = $link ? pg_last_error($link) : pg_last_error(); if ($s) { $s = str_replace('ERROR', 'errcode: 1000 ', $s); spip_log("$s - $query", 'pg.' . _LOG_ERREUR); } return $s; } // http://code.spip.net/@spip_pg_errno function spip_pg_errno($serveur = '') { // il faudrait avoir la derniere ressource retournee et utiliser // http://fr2.php.net/manual/fr/function.pg-result-error.php return 0; } // http://code.spip.net/@spip_pg_drop_table function spip_pg_drop_table($table, $exist = '', $serveur = '', $requeter = true) { if ($exist) { $exist = " IF EXISTS"; } if (spip_pg_query("DROP TABLE$exist $table", $serveur, $requeter)) { return true; } else { return false; } } // supprime une vue // http://code.spip.net/@spip_pg_drop_view function spip_pg_drop_view($view, $exist = '', $serveur = '', $requeter = true) { if ($exist) { $exist = " IF EXISTS"; } return spip_pg_query("DROP VIEW$exist $view", $serveur, $requeter); } /** * Retourne une ressource de la liste des tables de la base de données * * @param string $match * Filtre sur tables à récupérer * @param string $serveur * Connecteur de la base * @param bool $requeter * true pour éxecuter la requête * false pour retourner le texte de la requête. * @return ressource * Ressource à utiliser avec sql_fetch() **/ function spip_pg_showbase($match, $serveur = '', $requeter = true) { $connexion = &$GLOBALS['connexions'][$serveur ? strtolower($serveur) : 0]; $link = $connexion['link']; $connexion['last'] = $q = "SELECT tablename FROM pg_tables WHERE tablename ILIKE " . _q($match); return spip_pg_query_simple($link, $q); } // http://code.spip.net/@spip_pg_showtable function spip_pg_showtable($nom_table, $serveur = '', $requeter = true) { $connexion = &$GLOBALS['connexions'][$serveur ? strtolower($serveur) : 0]; $link = $connexion['link']; $connexion['last'] = $q = "SELECT column_name, column_default, data_type FROM information_schema.columns WHERE table_name ILIKE " . _q($nom_table); $res = spip_pg_query_simple($link, $q); if (!$res) { return false; } // etrangement, $res peut ne rien contenir, mais arriver ici... // il faut en tenir compte dans le return $fields = array(); while ($field = pg_fetch_array($res, null, PGSQL_NUM)) { $fields[$field[0]] = $field[2] . (!$field[1] ? '' : (" DEFAULT " . $field[1])); } $connexion['last'] = $q = "SELECT indexdef FROM pg_indexes WHERE tablename ILIKE " . _q($nom_table); $res = spip_pg_query_simple($link, $q); $keys = array(); while ($index = pg_fetch_array($res, null, PGSQL_NUM)) { if (preg_match('/CREATE\s+(UNIQUE\s+)?INDEX\s([^\s]+).*\((.*)\)$/', $index[0], $r)) { $nom = str_replace($nom_table . '_', '', $r[2]); $keys[($r[1] ? "PRIMARY KEY" : ("KEY " . $nom))] = $r[3]; } } return count($fields) ? array('field' => $fields, 'key' => $keys) : false; } // Fonction de creation d'une table SQL nommee $nom // a partir de 2 tableaux PHP : // champs: champ => type // cles: type-de-cle => champ(s) // si $autoinc, c'est une auto-increment (i.e. serial) sur la Primary Key // Le nom des index est prefixe par celui de la table pour eviter les conflits // http://code.spip.net/@spip_pg_create function spip_pg_create($nom, $champs, $cles, $autoinc = false, $temporary = false, $serveur = '', $requeter = true) { $connexion = $GLOBALS['connexions'][$serveur ? strtolower($serveur) : 0]; $link = $connexion['link']; $nom = prefixer_table_spip($nom, $connexion['prefixe']); $query = $prim = $prim_name = $v = $s = $p = ''; $keys = array(); // certains plugins declarent les tables (permet leur inclusion dans le dump) // sans les renseigner (laisse le compilo recuperer la description) if (!is_array($champs) || !is_array($cles)) { return; } foreach ($cles as $k => $v) { if (strpos($k, "KEY ") === 0) { $n = str_replace('`', '', $k); $v = str_replace('`', '"', $v); $i = $nom . preg_replace("/KEY +/", '_', $n); if ($k != $n) { $i = "\"$i\""; } $keys[] = "CREATE INDEX $i ON $nom ($v);"; } elseif (strpos($k, "UNIQUE ") === 0) { $k = preg_replace("/^UNIQUE +/", '', $k); $prim .= "$s\n\t\tCONSTRAINT " . str_replace('`', '"', $k) . " UNIQUE ($v)"; } else { $prim .= "$s\n\t\t" . str_replace('`', '"', $k) . " ($v)"; } if ($k == "PRIMARY KEY") { $prim_name = $v; } $s = ","; } $s = ''; $character_set = ""; if (@$GLOBALS['meta']['charset_sql_base']) { $character_set .= " CHARACTER SET " . $GLOBALS['meta']['charset_sql_base']; } if (@$GLOBALS['meta']['charset_collation_sql_base']) { $character_set .= " COLLATE " . $GLOBALS['meta']['charset_collation_sql_base']; } foreach ($champs as $k => $v) { $k = str_replace('`', '"', $k); if (preg_match(',([a-z]*\s*(\(\s*[0-9]*\s*\))?(\s*binary)?),i', $v, $defs)) { if (preg_match(',(char|text),i', $defs[1]) and !preg_match(',binary,i', $defs[1])) { $v = $defs[1] . $character_set . ' ' . substr($v, strlen($defs[1])); } } $query .= "$s\n\t\t$k " . (($autoinc && ($prim_name == $k) && preg_match(',\b(big|small|medium|tiny)?int\b,i', $v)) ? " bigserial" : mysql2pg_type($v) ); $s = ","; } $temporary = $temporary ? 'TEMPORARY' : ''; // En l'absence de "if not exists" en PG, on neutralise les erreurs $q = "CREATE $temporary TABLE $nom ($query" . ($prim ? ",$prim" : '') . ")" . ($character_set ? " DEFAULT $character_set" : "") . "\n"; if (!$requeter) { return $q; } $connexion['last'] = $q; $r = @pg_query($link, $q); if (!$r) { spip_log("Impossible de creer cette table: $q", 'pg.' . _LOG_ERREUR); } else { foreach ($keys as $index) { pg_query($link, $index); } } return $r; } function spip_pg_create_base($nom, $serveur = '', $requeter = true) { return spip_pg_query("CREATE DATABASE $nom", $serveur, $requeter); } // Fonction de creation d'une vue SQL nommee $nom // http://code.spip.net/@spip_pg_create_view function spip_pg_create_view($nom, $query_select, $serveur = '', $requeter = true) { if (!$query_select) { return false; } // vue deja presente if (sql_showtable($nom, false, $serveur)) { if ($requeter) { spip_log("Echec creation d'une vue sql ($nom) car celle-ci existe deja (serveur:$serveur)", 'pg.' . _LOG_ERREUR); } return false; } $query = "CREATE VIEW $nom AS " . $query_select; return spip_pg_query($query, $serveur, $requeter); } // http://code.spip.net/@spip_pg_set_connect_charset function spip_pg_set_connect_charset($charset, $serveur = '', $requeter = true) { spip_log("changement de charset sql a ecrire en PG", 'pg.' . _LOG_ERREUR); } /** * Optimise une table SQL * * @param $table nom de la table a optimiser * @param $serveur nom de la connexion * @param $requeter effectuer la requete ? sinon retourner son code * @return bool|string true / false / requete **/ // http://code.spip.net/@spip_sqlite_optimize function spip_pg_optimize($table, $serveur = '', $requeter = true) { return spip_pg_query("VACUUM " . $table, $serveur, $requeter); } // Selectionner la sous-chaine dans $objet // correspondant a $lang. Cf balise Multi de Spip // http://code.spip.net/@spip_pg_multi function spip_pg_multi($objet, $lang) { $r = "regexp_replace(" . $objet . ",'<multi>.*[[]" . $lang . "[]]([^[]*).*</multi>', E'\\\\1') AS multi"; return $r; } // Palanquee d'idiosyncrasies MySQL dans les creations de table // A completer par les autres, mais essayer de reduire en amont. // http://code.spip.net/@mysql2pg_type function mysql2pg_type($v) { $remplace = array( '/auto_increment/i' => '', // non reconnu '/bigint/i' => 'bigint', '/mediumint/i' => 'mediumint', '/smallint/i' => 'smallint', "/tinyint/i" => 'int', '/int\s*[(]\s*\d+\s*[)]/i' => 'int', "/longtext/i" => 'text', "/mediumtext/i" => 'text', "/tinytext/i" => 'text', "/longblob/i" => 'text', "/0000-00-00/" => '0001-01-01', "/datetime/i" => 'timestamp', "/unsigned/i" => '', "/double/i" => 'double precision', '/VARCHAR\((\d+)\)\s+BINARY/i' => 'varchar(\1)', "/ENUM *[(][^)]*[)]/i" => "varchar(255)", '/(timestamp .* )ON .*$/is' => '\\1', ); return preg_replace(array_keys($remplace), array_values($remplace), $v); } // Renvoie false si on n'a pas les fonctions pg (pour l'install) // http://code.spip.net/@spip_versions_pg function spip_versions_pg() { charger_php_extension('pgsql'); return function_exists('pg_connect'); } mysql.php 0000666 00000146700 15245543621 0006445 0 ustar 00 <?php /* *************************************************************************\ * SPIP, Systeme de publication pour l'internet * * * * Copyright (c) 2001-2019 * * Arnaud Martin, Antoine Pitrou, Philippe Riviere, Emmanuel Saint-James * * * * Ce programme est un logiciel libre distribue sous licence GNU/GPL. * * Pour plus de details voir le fichier COPYING.txt ou l'aide en ligne. * \***************************************************************************/ /** * Ce fichier contient les fonctions gérant * les instructions SQL pour MySQL * * Ces instructions utilisent la librairie PHP Mysqli * * @package SPIP\Core\SQL\MySQL */ if (!defined('_ECRIRE_INC_VERSION')) { return; } if (!defined('_MYSQL_NOPLANES')) { define('_MYSQL_NOPLANES', true); } /** * Crée la première connexion à un serveur MySQL via MySQLi * * @param string $host Chemin du serveur * @param int $port Port de connexion * @param string $login Nom d'utilisateur * @param string $pass Mot de passe * @param string $db Nom de la base * @param string $prefixe Préfixe des tables SPIP * @return array|bool * - false si la connexion a échoué * - tableau décrivant la connexion sinon */ function req_mysql_dist($host, $port, $login, $pass, $db = '', $prefixe = '') { if (!charger_php_extension('mysqli')) { return false; } // si port est fourni mais pas host, c'est un socket -> compat avec vieille syntaxe de mysql_connect() et anciens fichiers connect.php if ( $port and !is_numeric($socket = $port) and (!$host or $host=='localhost')) { $link = @mysqli_connect($host, $login, $pass, '', null, $socket); } elseif ($port) { $link = @mysqli_connect($host, $login, $pass, '', $port); } else { $link = @mysqli_connect($host, $login, $pass); } if (!$link) { spip_log('Echec mysqli_connect. Erreur : ' . mysqli_connect_error(), 'mysql.' . _LOG_HS); return false; } $last = ''; if (!$db) { $ok = $link; $db = 'spip'; } else { $ok = mysqli_select_db($link, $db); if (defined('_MYSQL_SET_SQL_MODE') or defined('_MYSQL_SQL_MODE_TEXT_NOT_NULL') // compatibilite ) { mysqli_query($link, $last = "set sql_mode=''"); } } spip_log("Connexion MySQLi vers $host, base $db, prefixe $prefixe " . ($ok ? "operationnelle" : 'impossible'), _LOG_DEBUG); return !$ok ? false : array( 'db' => $db, 'last' => $last, 'prefixe' => $prefixe ? $prefixe : $db, 'link' => $link, 'total_requetes' => 0, ); } $GLOBALS['spip_mysql_functions_1'] = array( 'alter' => 'spip_mysql_alter', 'count' => 'spip_mysql_count', 'countsel' => 'spip_mysql_countsel', 'create' => 'spip_mysql_create', 'create_base' => 'spip_mysql_create_base', 'create_view' => 'spip_mysql_create_view', 'date_proche' => 'spip_mysql_date_proche', 'delete' => 'spip_mysql_delete', 'drop_table' => 'spip_mysql_drop_table', 'drop_view' => 'spip_mysql_drop_view', 'errno' => 'spip_mysql_errno', 'error' => 'spip_mysql_error', 'explain' => 'spip_mysql_explain', 'fetch' => 'spip_mysql_fetch', 'seek' => 'spip_mysql_seek', 'free' => 'spip_mysql_free', 'hex' => 'spip_mysql_hex', 'in' => 'spip_mysql_in', 'insert' => 'spip_mysql_insert', 'insertq' => 'spip_mysql_insertq', 'insertq_multi' => 'spip_mysql_insertq_multi', 'listdbs' => 'spip_mysql_listdbs', 'multi' => 'spip_mysql_multi', 'optimize' => 'spip_mysql_optimize', 'query' => 'spip_mysql_query', 'quote' => 'spip_mysql_quote', 'replace' => 'spip_mysql_replace', 'replace_multi' => 'spip_mysql_replace_multi', 'repair' => 'spip_mysql_repair', 'select' => 'spip_mysql_select', 'selectdb' => 'spip_mysql_selectdb', 'set_charset' => 'spip_mysql_set_charset', 'get_charset' => 'spip_mysql_get_charset', 'showbase' => 'spip_mysql_showbase', 'showtable' => 'spip_mysql_showtable', 'update' => 'spip_mysql_update', 'updateq' => 'spip_mysql_updateq', // association de chaque nom http d'un charset aux couples MySQL 'charsets' => array( 'cp1250' => array('charset' => 'cp1250', 'collation' => 'cp1250_general_ci'), 'cp1251' => array('charset' => 'cp1251', 'collation' => 'cp1251_general_ci'), 'cp1256' => array('charset' => 'cp1256', 'collation' => 'cp1256_general_ci'), 'iso-8859-1' => array('charset' => 'latin1', 'collation' => 'latin1_swedish_ci'), //'iso-8859-6'=>array('charset'=>'latin1','collation'=>'latin1_swedish_ci'), 'iso-8859-9' => array('charset' => 'latin5', 'collation' => 'latin5_turkish_ci'), //'iso-8859-15'=>array('charset'=>'latin1','collation'=>'latin1_swedish_ci'), 'utf-8' => array('charset' => 'utf8', 'collation' => 'utf8_general_ci') ) ); /** * Retrouver un link d'une connexion MySQL via MySQLi * * @param string $serveur Nom du serveur * @return Object Information de connexion pour mysqli */ function _mysql_link($serveur = '') { $link = &$GLOBALS['connexions'][$serveur ? $serveur : 0]['link']; return $link; } /** * Définit un charset pour la connexion avec Mysql * * @param string $charset Charset à appliquer * @param string $serveur Nom de la connexion * @param bool $requeter inutilisé * @return resource Ressource de résultats pour fetch() */ function spip_mysql_set_charset($charset, $serveur = '', $requeter = true) { $connexion = &$GLOBALS['connexions'][$serveur ? strtolower($serveur) : 0]; spip_log("changement de charset sql : " . "SET NAMES " . _q($charset), _LOG_DEBUG); return mysqli_query($connexion['link'], $connexion['last'] = "SET NAMES " . _q($charset)); } /** * Teste si le charset indiqué est disponible sur le serveur SQL * * @param array|string $charset Nom du charset à tester. * @param string $serveur Nom de la connexion * @param bool $requeter inutilisé * @return array Description du charset (son nom est dans 'charset') */ function spip_mysql_get_charset($charset = array(), $serveur = '', $requeter = true) { $connexion = &$GLOBALS['connexions'][$serveur ? strtolower($serveur) : 0]; $connexion['last'] = $c = "SHOW CHARACTER SET" . (!$charset ? '' : (" LIKE " . _q($charset['charset']))); return spip_mysql_fetch(mysqli_query($connexion['link'], $c), null, $serveur); } /** * Exécute une requête Mysql (obsolète, ne plus utiliser) * * @deprecated Utiliser sql_query() ou autres * * @param string $query Requête * @param string $serveur Nom de la connexion * @param bool $requeter Exécuter la requête, sinon la retourner * @return Resource Ressource pour fetch() **/ function spip_query_db($query, $serveur = '', $requeter = true) { return spip_mysql_query($query, $serveur, $requeter); } /** * Exécute une requête MySQL, munie d'une trace à la demande * * @param string $query Requête * @param string $serveur Nom de la connexion * @param bool $requeter Exécuter la requête, sinon la retourner * @return array|resource|string|bool * - string : Texte de la requête si on ne l'exécute pas * - ressource|bool : Si requête exécutée * - array : Tableau décrivant requête et temps d'exécution si var_profile actif pour tracer. */ function spip_mysql_query($query, $serveur = '', $requeter = true) { $connexion = &$GLOBALS['connexions'][$serveur ? strtolower($serveur) : 0]; $prefixe = $connexion['prefixe']; $link = $connexion['link']; $db = $connexion['db']; $query = _mysql_traite_query($query, $db, $prefixe); // renvoyer la requete inerte si demandee if (!$requeter) { return $query; } if (isset($_GET['var_profile'])) { include_spip('public/tracer'); $t = trace_query_start(); } else { $t = 0; } $connexion['last'] = $query; $connexion['total_requetes']++; // ajouter un debug utile dans log/mysql-slow.log ? $debug = ''; if (defined('_DEBUG_SLOW_QUERIES') and _DEBUG_SLOW_QUERIES) { if (isset($GLOBALS['debug']['aucasou'])) { list(, $id, , $infos) = $GLOBALS['debug']['aucasou']; $debug .= "BOUCLE$id @ " . $infos[0] . " | "; } $debug .= $_SERVER['REQUEST_URI'] . ' + ' . $GLOBALS['ip']; $debug = ' /* ' . mysqli_real_escape_string($link, str_replace('*/', '@/', $debug)) . ' */'; } $r = mysqli_query($link, $query . $debug); //Eviter de propager le GoneAway sur les autres requetes d'un même processus PHP if ($e = spip_mysql_errno($serveur)) { // Log d'un Gone Away if ($e == 2006) { //Si Gone Away on relance une connexion vierge //Fermer la connexion defaillante mysqli_close($connexion['link']); unset($GLOBALS['connexions'][$serveur ? strtolower($serveur) : 0]); //Relancer une connexion vierge spip_connect($serveur); $connexion = &$GLOBALS['connexions'][$serveur ? strtolower($serveur) : 0]; $link = $connexion['link']; //On retente au cas où $r = mysqli_query($link, $query . $debug); } } // Log de l'erreur eventuelle if ($e = spip_mysql_errno($serveur)) { $e .= spip_mysql_error($query, $serveur); } // et du fautif return $t ? trace_query_end($query, $t, $r, $e, $serveur) : $r; } /** * Modifie une structure de table MySQL * * @param string $query Requête SQL (sans 'ALTER ') * @param string $serveur Nom de la connexion * @param bool $requeter Exécuter la requête, sinon la retourner * @return array|bool|string * - string : Texte de la requête si on ne l'exécute pas * - bool : Si requête exécutée * - array : Tableau décrivant requête et temps d'exécution si var_profile actif pour tracer. */ function spip_mysql_alter($query, $serveur = '', $requeter = true) { // ici on supprime les ` entourant le nom de table pour permettre // la transposition du prefixe, compte tenu que les plugins ont la mauvaise habitude // d'utiliser ceux-ci, copie-colle de phpmyadmin $query = preg_replace(",^TABLE\s*`([^`]*)`,i", "TABLE \\1", $query); return spip_mysql_query("ALTER " . $query, $serveur, $requeter); # i.e. que PG se debrouille } /** * Optimise une table MySQL * * @param string $table Nom de la table * @param string $serveur Nom de la connexion * @param bool $requeter inutilisé * @return bool Toujours true */ function spip_mysql_optimize($table, $serveur = '', $requeter = true) { spip_mysql_query("OPTIMIZE TABLE " . $table); return true; } /** * Retourne une explication de requête (Explain) MySQL * * @param string $query Texte de la requête * @param string $serveur Nom de la connexion * @param bool $requeter inutilisé * @return array Tableau de l'explication */ function spip_mysql_explain($query, $serveur = '', $requeter = true) { if (strpos(ltrim($query), 'SELECT') !== 0) { return array(); } $connexion = &$GLOBALS['connexions'][$serveur ? strtolower($serveur) : 0]; $prefixe = $connexion['prefixe']; $link = $connexion['link']; $db = $connexion['db']; $query = 'EXPLAIN ' . _mysql_traite_query($query, $db, $prefixe); $r = mysqli_query($link, $query); return spip_mysql_fetch($r, null, $serveur); } /** * Exécute une requête de sélection avec MySQL * * Instance de sql_select (voir ses specs). * * @see sql_select() * @note * Les `\n` et `\t` sont utiles au debusqueur. * * @param string|array $select Champs sélectionnés * @param string|array $from Tables sélectionnées * @param string|array $where Contraintes * @param string|array $groupby Regroupements * @param string|array $orderby Tris * @param string $limit Limites de résultats * @param string|array $having Contraintes posts sélections * @param string $serveur Nom de la connexion * @param bool $requeter Exécuter la requête, sinon la retourner * @return array|bool|resource|string * - string : Texte de la requête si on ne l'exécute pas * - ressource si requête exécutée, ressource pour fetch() * - false si la requête exécutée a ratée * - array : Tableau décrivant requête et temps d'exécution si var_profile actif pour tracer. */ function spip_mysql_select( $select, $from, $where = '', $groupby = '', $orderby = '', $limit = '', $having = '', $serveur = '', $requeter = true ) { $from = (!is_array($from) ? $from : spip_mysql_select_as($from)); $query = calculer_mysql_expression('SELECT', $select, ', ') . calculer_mysql_expression('FROM', $from, ', ') . calculer_mysql_expression('WHERE', $where) . calculer_mysql_expression('GROUP BY', $groupby, ',') . calculer_mysql_expression('HAVING', $having) . ($orderby ? ("\nORDER BY " . spip_mysql_order($orderby)) : '') . ($limit ? "\nLIMIT $limit" : ''); // renvoyer la requete inerte si demandee if ($requeter === false) { return $query; } $r = spip_mysql_query($query, $serveur, $requeter); return $r ? $r : $query; } /** * Prépare une clause order by * * Regroupe en texte les éléments si un tableau est donné * * @note * 0+x avec un champ x commencant par des chiffres est converti par MySQL * en le nombre qui commence x. Pas portable malheureusement, on laisse pour le moment. * * @param string|array $orderby Texte du orderby à préparer * @return string Texte du orderby préparé */ function spip_mysql_order($orderby) { return (is_array($orderby)) ? join(", ", $orderby) : $orderby; } /** * Prépare une clause WHERE pour MySQL * * Retourne une chaîne avec les bonnes parenthèses pour la * contrainte indiquée, au format donnée par le compilateur * * @param array|string $v * Description des contraintes * - string : Texte du where * - sinon tableau : A et B peuvent être de type string ou array, * OP et C sont de type string : * - array(A) : A est le texte du where * - array(OP, A) : contrainte OP( A ) * - array(OP, A, B) : contrainte (A OP B) * - array(OP, A, B, C) : contrainte (A OP (B) : C) * @return string * Contrainte pour clause WHERE */ function calculer_mysql_where($v) { if (!is_array($v)) { return $v; } $op = array_shift($v); if (!($n = count($v))) { return $op; } else { $arg = calculer_mysql_where(array_shift($v)); if ($n == 1) { return "$op($arg)"; } else { $arg2 = calculer_mysql_where(array_shift($v)); if ($n == 2) { return "($arg $op $arg2)"; } else { return "($arg $op ($arg2) : $v[0])"; } } } } /** * Calcule un expression pour une requête, en cumulant chaque élément * avec l'opérateur de liaison ($join) indiqué * * Renvoie grosso modo "$expression join($join, $v)" * * @param string $expression Mot clé de l'expression, tel que "WHERE" ou "ORDER BY" * @param array|string $v Données de l'expression * @param string $join Si les données sont un tableau, elles seront groupées par cette jointure * @return string Texte de l'expression, une partie donc, du texte la requête. */ function calculer_mysql_expression($expression, $v, $join = 'AND') { if (empty($v)) { return ''; } $exp = "\n$expression "; if (!is_array($v)) { return $exp . $v; } else { if (strtoupper($join) === 'AND') { return $exp . join("\n\t$join ", array_map('calculer_mysql_where', $v)); } else { return $exp . join($join, $v); } } } /** * Renvoie des `nom AS alias` * * @param array $args * @return string Sélection de colonnes pour une clause SELECT */ function spip_mysql_select_as($args) { $res = ''; foreach ($args as $k => $v) { if (substr($k, -1) == '@') { // c'est une jointure qui se refere au from precedent // pas de virgule $res .= ' ' . $v; } else { if (!is_numeric($k)) { $p = strpos($v, " "); if ($p) { $v = substr($v, 0, $p) . " AS `$k`" . substr($v, $p); } else { $v .= " AS `$k`"; } } $res .= ', ' . $v; } } return substr($res, 2); } /** * Changer les noms des tables ($table_prefix) * * TODO: Quand tous les appels SQL seront abstraits on pourra l'améliorer */ define('_SQL_PREFIXE_TABLE_MYSQL', '/([,\s])spip_/S'); /** * Prépare le texte d'une requête avant son exécution * * Change les préfixes de tables SPIP par ceux véritables * * @param string $query Requête à préparer * @param string $db Nom de la base de donnée * @param string $prefixe Préfixe de tables à appliquer * @return string Requête préparée */ function _mysql_traite_query($query, $db = '', $prefixe = '') { if ($GLOBALS['mysql_rappel_nom_base'] and $db) { $pref = '`' . $db . '`.'; } else { $pref = ''; } if ($prefixe) { $pref .= $prefixe . "_"; } if (!preg_match('/\s(SET|VALUES|WHERE|DATABASE)\s/i', $query, $regs)) { $suite = ''; } else { $suite = strstr($query, $regs[0]); $query = substr($query, 0, -strlen($suite)); // propager le prefixe en cas de requete imbriquee // il faut alors echapper les chaine avant de le faire, pour ne pas risquer de // modifier une requete qui est en fait juste du texte dans un champ if (stripos($suite, "SELECT") !== false) { list($suite, $textes) = query_echappe_textes($suite); if (preg_match('/^(.*?)([(]\s*SELECT\b.*)$/si', $suite, $r)) { $suite = $r[1] . _mysql_traite_query($r[2], $db, $prefixe); } $suite = query_reinjecte_textes($suite, $textes); } } $r = preg_replace(_SQL_PREFIXE_TABLE_MYSQL, '\1' . $pref, $query) . $suite; // en option, remplacer les emoji (que mysql ne sait pas gérer) en 💩 if (defined('_MYSQL_NOPLANES') and _MYSQL_NOPLANES and lire_meta('charset_sql_connexion') == 'utf8') { include_spip('inc/charsets'); $r = utf8_noplanes($r); } #spip_log("_mysql_traite_query: " . substr($r,0, 50) . ".... $db, $prefixe", _LOG_DEBUG); return $r; } /** * Sélectionne une base de données * * @param string $db * Nom de la base à utiliser * @param string $serveur * Nom du connecteur * @param bool $requeter * Inutilisé * * @return bool * - True cas de succès. * - False en cas d'erreur. **/ function spip_mysql_selectdb($db, $serveur = '', $requeter = true) { $link = _mysql_link($serveur); $ok = mysqli_select_db($link, $db); if (!$ok) { spip_log('Echec mysqli_selectdb. Erreur : ' . mysqli_error($link), 'mysql.' . _LOG_CRITIQUE); } return $ok; } /** * Retourne les bases de données accessibles * * Retourne un tableau du nom de toutes les bases de données * accessibles avec les permissions de l'utilisateur SQL * de cette connexion. * * Attention on n'a pas toujours les droits ! * * @param string $serveur * Nom du connecteur * @param bool $requeter * Inutilisé * @return array * Liste de noms de bases de données **/ function spip_mysql_listdbs($serveur = '', $requeter = true) { $dbs = array(); if ($res = spip_mysql_query("SHOW DATABASES", $serveur)) { while ($row = mysqli_fetch_assoc($res)) { $dbs[] = $row['Database']; } } return $dbs; } /** * Crée une table SQL * * Crée une table SQL nommee `$nom` à partir des 2 tableaux `$champs` et `$cles` * * @note Le nom des caches doit être inferieur à 64 caractères * * @param string $nom Nom de la table SQL * @param array $champs Couples (champ => description SQL) * @param array $cles Couples (type de clé => champ(s) de la clé) * @param bool $autoinc True pour ajouter un auto-incrément sur la Primary Key * @param bool $temporary True pour créer une table temporaire * @param string $serveur Nom de la connexion * @param bool $requeter inutilisé * @return array|null|resource|string * - null si champs ou cles n'est pas un tableau * - true si la requête réussie, false sinon. */ function spip_mysql_create( $nom, $champs, $cles, $autoinc = false, $temporary = false, $serveur = '', $requeter = true ) { $query = ''; $keys = ''; $s = ''; $p = ''; // certains plugins declarent les tables (permet leur inclusion dans le dump) // sans les renseigner (laisse le compilo recuperer la description) if (!is_array($champs) || !is_array($cles)) { return; } $res = spip_mysql_query("SELECT version() as v", $serveur); if (($row = mysqli_fetch_array($res)) && (version_compare($row['v'], '5.0', '>='))) { spip_mysql_query("SET sql_mode=''", $serveur); } foreach ($cles as $k => $v) { $keys .= "$s\n\t\t$k ($v)"; if ($k == "PRIMARY KEY") { $p = $v; } $s = ","; } $s = ''; $character_set = ""; if (@$GLOBALS['meta']['charset_sql_base']) { $character_set .= " CHARACTER SET " . $GLOBALS['meta']['charset_sql_base']; } if (@$GLOBALS['meta']['charset_collation_sql_base']) { $character_set .= " COLLATE " . $GLOBALS['meta']['charset_collation_sql_base']; } foreach ($champs as $k => $v) { $v = _mysql_remplacements_definitions_table($v); if (preg_match(',([a-z]*\s*(\(\s*[0-9]*\s*\))?(\s*binary)?),i', $v, $defs)) { if (preg_match(',(char|text),i', $defs[1]) and !preg_match(',(binary|CHARACTER|COLLATE),i', $v) ) { $v = $defs[1] . $character_set . ' ' . substr($v, strlen($defs[1])); } } $query .= "$s\n\t\t$k $v" . (($autoinc && ($p == $k) && preg_match(',\b(big|small|medium)?int\b,i', $v)) ? " auto_increment" : '' ); $s = ","; } $temporary = $temporary ? 'TEMPORARY' : ''; $q = "CREATE $temporary TABLE IF NOT EXISTS $nom ($query" . ($keys ? ",$keys" : '') . ")" . " ENGINE=MyISAM" . ($character_set ? " DEFAULT $character_set" : "") . "\n"; return spip_mysql_query($q, $serveur); } /** * Adapte pour Mysql la déclaration SQL d'une colonne d'une table * * @param string $query * Définition SQL d'un champ de table * @return string * Définition SQL adaptée pour MySQL d'un champ de table */ function _mysql_remplacements_definitions_table($query) { // quelques remplacements $num = "(\s*\([0-9]*\))?"; $enum = "(\s*\([^\)]*\))?"; $remplace = array( '/VARCHAR(\s*[^\s\(])/is' => 'VARCHAR(255)\\1', ); $query = preg_replace(array_keys($remplace), $remplace, $query); return $query; } /** * Crée une base de données MySQL * * @param string $nom Nom de la base * @param string $serveur Nom de la connexion * @param bool $requeter Exécuter la requête, sinon la retourner * @return bool true si la base est créee. **/ function spip_mysql_create_base($nom, $serveur = '', $requeter = true) { return spip_mysql_query("CREATE DATABASE `$nom`", $serveur, $requeter); } /** * Crée une vue SQL nommée `$nom` * * @param string $nom * Nom de la vue à creer * @param string $query_select * Texte de la requête de sélection servant de base à la vue * @param string $serveur * Nom du connecteur * @param bool $requeter * Effectuer la requete, sinon la retourner * @return bool|string * - true si la vue est créée * - false si erreur ou si la vue existe déja * - string texte de la requête si $requeter vaut false */ function spip_mysql_create_view($nom, $query_select, $serveur = '', $requeter = true) { if (!$query_select) { return false; } // vue deja presente if (sql_showtable($nom, false, $serveur)) { spip_log("Echec creation d'une vue sql ($nom) car celle-ci existe deja (serveur:$serveur)", _LOG_ERREUR); return false; } $query = "CREATE VIEW $nom AS " . $query_select; return spip_mysql_query($query, $serveur, $requeter); } /** * Supprime une table SQL * * @param string $table Nom de la table SQL * @param string $exist True pour ajouter un test d'existence avant de supprimer * @param string $serveur Nom de la connexion * @param bool $requeter Exécuter la requête, sinon la retourner * @return bool|string * - string Texte de la requête si demandé * - true si la requête a réussie, false sinon */ function spip_mysql_drop_table($table, $exist = '', $serveur = '', $requeter = true) { if ($exist) { $exist = " IF EXISTS"; } return spip_mysql_query("DROP TABLE$exist $table", $serveur, $requeter); } /** * Supprime une vue SQL * * @param string $view Nom de la vue SQL * @param string $exist True pour ajouter un test d'existence avant de supprimer * @param string $serveur Nom de la connexion * @param bool $requeter Exécuter la requête, sinon la retourner * @return bool|string * - string Texte de la requête si demandé * - true si la requête a réussie, false sinon */ function spip_mysql_drop_view($view, $exist = '', $serveur = '', $requeter = true) { if ($exist) { $exist = " IF EXISTS"; } return spip_mysql_query("DROP VIEW$exist $view", $serveur, $requeter); } /** * Retourne une ressource de la liste des tables de la base de données * * @param string $match * Filtre sur tables à récupérer * @param string $serveur * Connecteur de la base * @param bool $requeter * true pour éxecuter la requête * false pour retourner le texte de la requête. * @return ressource * Ressource à utiliser avec sql_fetch() **/ function spip_mysql_showbase($match, $serveur = '', $requeter = true) { return spip_mysql_query("SHOW TABLES LIKE " . _q($match), $serveur, $requeter); } /** * Répare une table SQL * * Utilise `REPAIR TABLE ...` de MySQL * * @param string $table Nom de la table SQL * @param string $serveur Nom de la connexion * @param bool $requeter Exécuter la requête, sinon la retourner * @return bool|string * - string Texte de la requête si demandée, * - true si la requête a réussie, false sinon */ function spip_mysql_repair($table, $serveur = '', $requeter = true) { return spip_mysql_query("REPAIR TABLE `$table`", $serveur, $requeter); } define('_MYSQL_RE_SHOW_TABLE', '/^[^(),]*\(((?:[^()]*\((?:[^()]*\([^()]*\))?[^()]*\)[^()]*)*[^()]*)\)[^()]*$/'); /** * Obtient la description d'une table ou vue MySQL * * Récupère la définition d'une table ou d'une vue avec colonnes, indexes, etc. * au même format que la définition des tables SPIP, c'est à dire * un tableau avec les clés * * - `field` (tableau colonne => description SQL) et * - `key` (tableau type de clé => colonnes) * * @param string $nom_table Nom de la table SQL * @param string $serveur Nom de la connexion * @param bool $requeter Exécuter la requête, sinon la retourner * @return array|string * - chaîne vide si pas de description obtenue * - string Texte de la requête si demandé * - array description de la table sinon */ function spip_mysql_showtable($nom_table, $serveur = '', $requeter = true) { $s = spip_mysql_query("SHOW CREATE TABLE `$nom_table`", $serveur, $requeter); if (!$s) { return ''; } if (!$requeter) { return $s; } list(, $a) = mysqli_fetch_array($s, MYSQLI_NUM); if (preg_match(_MYSQL_RE_SHOW_TABLE, $a, $r)) { $desc = $r[1]; // extraction d'une KEY éventuelle en prenant garde de ne pas // relever un champ dont le nom contient KEY (ex. ID_WHISKEY) if (preg_match("/^(.*?),([^,]*\sKEY[ (].*)$/s", $desc, $r)) { $namedkeys = $r[2]; $desc = $r[1]; } else { $namedkeys = ""; } $fields = array(); foreach (preg_split("/,\s*`/", $desc) as $v) { preg_match("/^\s*`?([^`]*)`\s*(.*)/", $v, $r); $fields[strtolower($r[1])] = $r[2]; } $keys = array(); foreach (preg_split('/\)\s*(,|$)/', $namedkeys) as $v) { if (preg_match("/^\s*([^(]*)\(([^(]*(\(\d+\))?)$/", $v, $r)) { $k = str_replace("`", '', trim($r[1])); $t = strtolower(str_replace("`", '', $r[2])); if ($k && !isset($keys[$k])) { $keys[$k] = $t; } else { $keys[] = $t; } } } spip_mysql_free($s); return array('field' => $fields, 'key' => $keys); } $res = spip_mysql_query("SHOW COLUMNS FROM `$nom_table`", $serveur); if ($res) { $nfields = array(); $nkeys = array(); while ($val = spip_mysql_fetch($res)) { $nfields[$val["Field"]] = $val['Type']; if ($val['Null'] == 'NO') { $nfields[$val["Field"]] .= ' NOT NULL'; } if ($val['Default'] === '0' || $val['Default']) { if (preg_match('/[A-Z_]/', $val['Default'])) { $nfields[$val["Field"]] .= ' DEFAULT ' . $val['Default']; } else { $nfields[$val["Field"]] .= " DEFAULT '" . $val['Default'] . "'"; } } if ($val['Extra']) { $nfields[$val["Field"]] .= ' ' . $val['Extra']; } if ($val['Key'] == 'PRI') { $nkeys['PRIMARY KEY'] = $val["Field"]; } else { if ($val['Key'] == 'MUL') { $nkeys['KEY ' . $val["Field"]] = $val["Field"]; } else { if ($val['Key'] == 'UNI') { $nkeys['UNIQUE KEY ' . $val["Field"]] = $val["Field"]; } } } } spip_mysql_free($res); return array('field' => $nfields, 'key' => $nkeys); } return ""; } /** * Rècupère une ligne de résultat * * Récupère la ligne suivante d'une ressource de résultat * * @param Ressource $r Ressource de résultat (issu de sql_select) * @param string $t Structure de résultat attendu (défaut MYSQLI_ASSOC) * @param string $serveur Nom de la connexion * @param bool $requeter Inutilisé * @return array Ligne de résultat */ function spip_mysql_fetch($r, $t = '', $serveur = '', $requeter = true) { if (!$t) { $t = MYSQLI_ASSOC; } if ($r) { return mysqli_fetch_array($r, $t); } } /** * Place le pointeur de résultat sur la position indiquée * * @param Ressource $r Ressource de résultat * @param int $row_number Position. Déplacer le pointeur à cette ligne * @param string $serveur Nom de la connexion * @param bool $requeter Inutilisé * @return bool True si déplacement réussi, false sinon. **/ function spip_mysql_seek($r, $row_number, $serveur = '', $requeter = true) { if ($r and mysqli_num_rows($r)) { return mysqli_data_seek($r, $row_number); } } /** * Retourne le nombre de lignes d'une sélection * * @param array|string $from Tables à consulter (From) * @param array|string $where Conditions a remplir (Where) * @param array|string $groupby Critère de regroupement (Group by) * @param array $having Tableau des des post-conditions à remplir (Having) * @param string $serveur Nom de la connexion * @param bool $requeter Exécuter la requête, sinon la retourner * @return int|string * - String Texte de la requête si demandé * - int Nombre de lignes (0 si la requête n'a pas réussie) **/ function spip_mysql_countsel( $from = array(), $where = array(), $groupby = '', $having = array(), $serveur = '', $requeter = true ) { $c = !$groupby ? '*' : ('DISTINCT ' . (is_string($groupby) ? $groupby : join(',', $groupby))); $r = spip_mysql_select("COUNT($c)", $from, $where, '', '', '', $having, $serveur, $requeter); if (!$requeter) { return $r; } if (!$r instanceof mysqli_result) { return 0; } list($c) = mysqli_fetch_array($r, MYSQLI_NUM); mysqli_free_result($r); return $c; } /** * Retourne la dernière erreur generée * * @note * Bien spécifier le serveur auquel on s'adresse, * mais à l'install la globale n'est pas encore complètement définie. * * @uses sql_error_backtrace() * * @param string $query * Requête qui était exécutée * @param string $serveur * Nom de la connexion * @param bool $requeter * Inutilisé * @return string * Erreur eventuelle **/ function spip_mysql_error($query = '', $serveur = '', $requeter = true) { $link = $GLOBALS['connexions'][$serveur ? strtolower($serveur) : 0]['link']; $s = mysqli_error($link); if ($s) { $trace = debug_backtrace(); if ($trace[0]['function'] != "spip_mysql_error") { spip_log("$s - $query - " . sql_error_backtrace(), 'mysql.' . _LOG_ERREUR); } } return $s; } /** * Retourne le numero de la dernière erreur SQL * * @param string $serveur * Nom de la connexion * @param bool $requeter * Inutilisé * @return int * 0, pas d'erreur. Autre, numéro de l'erreur. **/ function spip_mysql_errno($serveur = '', $requeter = true) { $link = $GLOBALS['connexions'][$serveur ? $serveur : 0]['link']; $s = mysqli_errno($link); // 2006 MySQL server has gone away // 2013 Lost connection to MySQL server during query if (in_array($s, array(2006, 2013))) { define('spip_interdire_cache', true); } if ($s) { spip_log("Erreur mysql $s", _LOG_ERREUR); } return $s; } /** * Retourne le nombre de lignes d’une ressource de sélection obtenue * avec `sql_select()` * * @param Ressource $r Ressource de résultat * @param string $serveur Nom de la connexion * @param bool $requeter Inutilisé * @return int Nombre de lignes */ function spip_mysql_count($r, $serveur = '', $requeter = true) { if ($r) { return mysqli_num_rows($r); } } /** * Libère une ressource de résultat * * Indique à MySQL de libérer de sa mémoire la ressoucre de résultat indiquée * car on n'a plus besoin de l'utiliser. * * @param Ressource $r Ressource de résultat * @param string $serveur Nom de la connexion * @param bool $requeter Inutilisé * @return bool True si réussi */ function spip_mysql_free($r, $serveur = '', $requeter = true) { return (($r instanceof mysqli_result) ? mysqli_free_result($r) : false); } /** * Insère une ligne dans une table * * @param string $table * Nom de la table SQL * @param string $champs * Liste des colonnes impactées, * @param string $valeurs * Liste des valeurs, * @param array $desc * Tableau de description des colonnes de la table SQL utilisée * (il sera calculé si nécessaire s'il n'est pas transmis). * @param string $serveur * Nom du connecteur * @param bool $requeter * Exécuter la requête, sinon la retourner * @return bool|string|int|array * - int|true identifiant de l'élément inséré (si possible), ou true, si réussite * - Texte de la requête si demandé, * - False en cas d'erreur, * - Tableau de description de la requête et du temps d'exécution, si var_profile activé **/ function spip_mysql_insert($table, $champs, $valeurs, $desc = array(), $serveur = '', $requeter = true) { $connexion = &$GLOBALS['connexions'][$serveur ? strtolower($serveur) : 0]; $link = $connexion['link']; $table = prefixer_table_spip($table, $connexion['prefixe']); $query = "INSERT INTO $table $champs VALUES $valeurs"; if (!$requeter) { return $query; } if (isset($_GET['var_profile'])) { include_spip('public/tracer'); $t = trace_query_start(); $e = ''; } else { $t = 0; } $connexion['last'] = $query; #spip_log($query, 'mysql.'._LOG_DEBUG); $r = false; if (mysqli_query($link, $query)) { $r = mysqli_insert_id($link); } else { // Log de l'erreur eventuelle if ($e = spip_mysql_errno($serveur)) { $e .= spip_mysql_error($query, $serveur); } // et du fautif } return $t ? trace_query_end($query, $t, $r, $e, $serveur) : $r; // return $r ? $r : (($r===0) ? -1 : 0); pb avec le multi-base. } /** * Insère une ligne dans une table, en protégeant chaque valeur * * @param string $table * Nom de la table SQL * @param string $couples * Couples (colonne => valeur) * @param array $desc * Tableau de description des colonnes de la table SQL utilisée * (il sera calculé si nécessaire s'il n'est pas transmis). * @param string $serveur * Nom du connecteur * @param bool $requeter * Exécuter la requête, sinon la retourner * @return bool|string|int|array * - int|true identifiant de l'élément inséré (si possible), ou true, si réussite * - Texte de la requête si demandé, * - False en cas d'erreur, * - Tableau de description de la requête et du temps d'exécution, si var_profile activé **/ function spip_mysql_insertq($table, $couples = array(), $desc = array(), $serveur = '', $requeter = true) { if (!$desc) { $desc = description_table($table, $serveur); } if (!$desc) { $couples = array(); } $fields = isset($desc['field']) ? $desc['field'] : array(); foreach ($couples as $champ => $val) { $couples[$champ] = spip_mysql_cite($val, $fields[$champ]); } return spip_mysql_insert($table, "(" . join(',', array_keys($couples)) . ")", "(" . join(',', $couples) . ")", $desc, $serveur, $requeter); } /** * Insère plusieurs lignes d'un coup dans une table * * @param string $table * Nom de la table SQL * @param array $tab_couples * Tableau de tableaux associatifs (colonne => valeur) * @param array $desc * Tableau de description des colonnes de la table SQL utilisée * (il sera calculé si nécessaire s'il n'est pas transmis). * @param string $serveur * Nom du connecteur * @param bool $requeter * Exécuter la requête, sinon la retourner * @return int|bool|string * - int|true identifiant du dernier élément inséré (si possible), ou true, si réussite * - Texte de la requête si demandé, * - False en cas d'erreur. **/ function spip_mysql_insertq_multi($table, $tab_couples = array(), $desc = array(), $serveur = '', $requeter = true) { if (!$desc) { $desc = description_table($table, $serveur); } if (!$desc) { $tab_couples = array(); } $fields = isset($desc['field']) ? $desc['field'] : array(); $cles = "(" . join(',', array_keys(reset($tab_couples))) . ')'; $valeurs = array(); $r = false; // Quoter et Inserer par groupes de 100 max pour eviter un debordement de pile foreach ($tab_couples as $couples) { foreach ($couples as $champ => $val) { $couples[$champ] = spip_mysql_cite($val, $fields[$champ]); } $valeurs[] = '(' . join(',', $couples) . ')'; if (count($valeurs) >= 100) { $r = spip_mysql_insert($table, $cles, join(', ', $valeurs), $desc, $serveur, $requeter); $valeurs = array(); } } if (count($valeurs)) { $r = spip_mysql_insert($table, $cles, join(', ', $valeurs), $desc, $serveur, $requeter); } return $r; // dans le cas d'une table auto_increment, le dernier insert_id } /** * Met à jour des enregistrements d'une table SQL * * @param string $table * Nom de la table * @param array $champs * Couples (colonne => valeur) * @param string|array $where * Conditions a remplir (Where) * @param array $desc * Tableau de description des colonnes de la table SQL utilisée * (il sera calculé si nécessaire s'il n'est pas transmis). * @param string $serveur * Nom de la connexion * @param bool $requeter * Exécuter la requête, sinon la retourner * @return array|bool|string * - string : texte de la requête si demandé * - true si la requête a réussie, false sinon * - array Tableau décrivant la requête et son temps d'exécution si var_profile est actif */ function spip_mysql_update($table, $champs, $where = '', $desc = array(), $serveur = '', $requeter = true) { $set = array(); foreach ($champs as $champ => $val) { $set[] = $champ . "=$val"; } if (!empty($set)) { return spip_mysql_query( calculer_mysql_expression('UPDATE', $table, ',') . calculer_mysql_expression('SET', $set, ',') . calculer_mysql_expression('WHERE', $where), $serveur, $requeter); } } /** * Met à jour des enregistrements d'une table SQL et protège chaque valeur * * Protège chaque valeur transmise avec sql_quote(), adapté au type * de champ attendu par la table SQL * * @note * Les valeurs sont des constantes à mettre entre apostrophes * sauf les expressions de date lorsqu'il s'agit de fonctions SQL (NOW etc) * * @param string $table * Nom de la table * @param array $champs * Couples (colonne => valeur) * @param string|array $where * Conditions a remplir (Where) * @param array $desc * Tableau de description des colonnes de la table SQL utilisée * (il sera calculé si nécessaire s'il n'est pas transmis). * @param string $serveur * Nom de la connexion * @param bool $requeter * Exécuter la requête, sinon la retourner * @return array|bool|string * - string : texte de la requête si demandé * - true si la requête a réussie, false sinon * - array Tableau décrivant la requête et son temps d'exécution si var_profile est actif */ function spip_mysql_updateq($table, $champs, $where = '', $desc = array(), $serveur = '', $requeter = true) { if (!$champs) { return; } if (!$desc) { $desc = description_table($table, $serveur); } if (!$desc) { $champs = array(); } else { $fields = $desc['field']; } $set = array(); foreach ($champs as $champ => $val) { $set[] = $champ . '=' . spip_mysql_cite($val, @$fields[$champ]); } return spip_mysql_query( calculer_mysql_expression('UPDATE', $table, ',') . calculer_mysql_expression('SET', $set, ',') . calculer_mysql_expression('WHERE', $where), $serveur, $requeter); } /** * Supprime des enregistrements d'une table * * @param string $table Nom de la table SQL * @param string|array $where Conditions à vérifier * @param string $serveur Nom du connecteur * @param bool $requeter Exécuter la requête, sinon la retourner * @return bool|string * - int : nombre de suppressions réalisées, * - Texte de la requête si demandé, * - False en cas d'erreur. **/ function spip_mysql_delete($table, $where = '', $serveur = '', $requeter = true) { $res = spip_mysql_query( calculer_mysql_expression('DELETE FROM', $table, ',') . calculer_mysql_expression('WHERE', $where), $serveur, $requeter); if (!$requeter) { return $res; } if ($res) { $link = _mysql_link($serveur); return mysqli_affected_rows($link); } else { return false; } } /** * Insère où met à jour une entrée d’une table SQL * * La clé ou les cles primaires doivent être présentes dans les données insérés. * La fonction effectue une protection automatique des données. * * Préférez updateq ou insertq. * * @param string $table * Nom de la table SQL * @param array $couples * Couples colonne / valeur à modifier, * @param array $desc * Tableau de description des colonnes de la table SQL utilisée * (il sera calculé si nécessaire s'il n'est pas transmis). * @param string $serveur * Nom du connecteur * @param bool $requeter * Exécuter la requête, sinon la retourner * @return bool|string * - true si réussite * - Texte de la requête si demandé, * - False en cas d'erreur. **/ function spip_mysql_replace($table, $couples, $desc = array(), $serveur = '', $requeter = true) { return spip_mysql_query("REPLACE $table (" . join(',', array_keys($couples)) . ') VALUES (' . join(',', array_map('_q', $couples)) . ')', $serveur, $requeter); } /** * Insère où met à jour des entrées d’une table SQL * * La clé ou les cles primaires doivent être présentes dans les données insérés. * La fonction effectue une protection automatique des données. * * Préférez insertq_multi et sql_updateq * * @param string $table * Nom de la table SQL * @param array $tab_couples * Tableau de tableau (colonne / valeur à modifier), * @param array $desc * Tableau de description des colonnes de la table SQL utilisée * (il sera calculé si nécessaire s'il n'est pas transmis). * @param string $serveur * Nom du connecteur * @param bool $requeter * Exécuter la requête, sinon la retourner * @return bool|string * - true si réussite * - Texte de la requête si demandé, * - False en cas d'erreur. **/ function spip_mysql_replace_multi($table, $tab_couples, $desc = array(), $serveur = '', $requeter = true) { $cles = "(" . join(',', array_keys($tab_couples[0])) . ')'; $valeurs = array(); foreach ($tab_couples as $couples) { $valeurs[] = '(' . join(',', array_map('_q', $couples)) . ')'; } $valeurs = implode(', ', $valeurs); return spip_mysql_query("REPLACE $table $cles VALUES $valeurs", $serveur, $requeter); } /** * Retourne l'instruction SQL pour obtenir le texte d'un champ contenant * une balise `<multi>` dans la langue indiquée * * Cette sélection est mise dans l'alias `multi` (instruction AS multi). * * @param string $objet Colonne ayant le texte * @param string $lang Langue à extraire * @return string Texte de sélection pour la requête */ function spip_mysql_multi($objet, $lang) { $lengthlang = strlen("[$lang]"); $posmulti = "INSTR(" . $objet . ", '<multi>')"; $posfinmulti = "INSTR(" . $objet . ", '</multi>')"; $debutchaine = "LEFT(" . $objet . ", $posmulti-1)"; $finchaine = "RIGHT(" . $objet . ", CHAR_LENGTH(" . $objet . ") -(7+$posfinmulti))"; $chainemulti = "TRIM(SUBSTRING(" . $objet . ", $posmulti+7, $posfinmulti -(7+$posmulti)))"; $poslang = "INSTR($chainemulti,'[" . $lang . "]')"; $poslang = "IF($poslang=0,INSTR($chainemulti,']')+1,$poslang+$lengthlang)"; $chainelang = "TRIM(SUBSTRING(" . $objet . ", $posmulti+7+$poslang-1,$posfinmulti -($posmulti+7+$poslang-1) ))"; $posfinlang = "INSTR(" . $chainelang . ", '[')"; $chainelang = "IF($posfinlang>0,LEFT($chainelang,$posfinlang-1),$chainelang)"; //$chainelang = "LEFT($chainelang,$posfinlang-1)"; $retour = "(TRIM(IF($posmulti = 0 , " . " TRIM(" . $objet . "), " . " CONCAT( " . " $debutchaine, " . " IF( " . " $poslang = 0, " . " $chainemulti, " . " $chainelang" . " ), " . " $finchaine" . " ) " . "))) AS multi"; return $retour; } /** * Prépare une chaîne hexadécimale * * Par exemple : FF ==> 0xFF en MySQL * * @param string $v * Chaine hexadecimale * @return string * Valeur hexadécimale pour MySQL **/ function spip_mysql_hex($v) { return "0x" . $v; } /** * Échapper une valeur selon son type ou au mieux * comme le fait `_q()` mais pour MySQL avec ses spécificités * * @param string|array|number $v * Texte, nombre ou tableau à échapper * @param string $type * Description du type attendu * (par exemple description SQL de la colonne recevant la donnée) * @return string|number * Donnée prête à être utilisée par le gestionnaire SQL */ function spip_mysql_quote($v, $type = '') { if ($type) { if (!is_array($v)) { return spip_mysql_cite($v, $type); } // si c'est un tableau, le parcourir en propageant le type foreach ($v as $k => $r) { $v[$k] = spip_mysql_quote($r, $type); } return $v; } // si on ne connait pas le type, s'en remettre a _q : // on ne fera pas mieux else { return _q($v); } } /** * Tester si une date est proche de la valeur d'un champ * * @param string $champ * Nom du champ a tester * @param int $interval * Valeur de l'intervalle : -1, 4, ... * @param string $unite * Utité utilisée (DAY, MONTH, YEAR, ...) * @return string * Expression SQL **/ function spip_mysql_date_proche($champ, $interval, $unite) { return '(' . $champ . (($interval <= 0) ? '>' : '<') . (($interval <= 0) ? 'DATE_SUB' : 'DATE_ADD') . '(' . sql_quote(date('Y-m-d H:i:s')) . ', INTERVAL ' . (($interval > 0) ? $interval : (0 - $interval)) . ' ' . $unite . '))'; } /** * Retourne une expression IN pour le gestionnaire de base de données * * IN (...) est limité à 255 éléments, d'où cette fonction assistante * * @param string $val * Colonne SQL sur laquelle appliquer le test * @param string|array $valeurs * Liste des valeurs possibles (séparés par des virgules si string) * @param string $not * - '' sélectionne les éléments correspondant aux valeurs * - 'NOT' inverse en sélectionnant les éléments ne correspondant pas aux valeurs * @param string $serveur * Nom du connecteur * @param bool $requeter * Inutilisé * @return string * Expression de requête SQL **/ function spip_mysql_in($val, $valeurs, $not = '', $serveur = '', $requeter = true) { $n = $i = 0; $in_sql = ""; while ($n = strpos($valeurs, ',', $n + 1)) { if ((++$i) >= 255) { $in_sql .= "($val $not IN (" . substr($valeurs, 0, $n) . "))\n" . ($not ? "AND\t" : "OR\t"); $valeurs = substr($valeurs, $n + 1); $i = $n = 0; } } $in_sql .= "($val $not IN ($valeurs))"; return "($in_sql)"; } /** * Retourne une expression IN pour le gestionnaire de base de données * * Pour compatibilité. Ne plus utiliser. * * @deprecated Utiliser sql_in() * * @param string $val Nom de la colonne * @param string|array $valeurs Valeurs * @param string $not NOT pour inverser * @return string Expression de requête SQL */ function calcul_mysql_in($val, $valeurs, $not = '') { if (is_array($valeurs)) { $valeurs = join(',', array_map('_q', $valeurs)); } elseif ($valeurs[0] === ',') { $valeurs = substr($valeurs, 1); } if (!strlen(trim($valeurs))) { return ($not ? "0=0" : '0=1'); } return spip_mysql_in($val, $valeurs, $not); } /** * Renvoie les bons echappements (mais pas sur les fonctions comme NOW()) * * @param string|number $v Texte ou nombre à échapper * @param string $type Type de donnée attendue, description SQL de la colonne de destination * @return string|number Texte ou nombre échappé */ function spip_mysql_cite($v, $type) { if (is_null($v) and stripos($type, "NOT NULL") === false ) { return 'NULL'; } // null php se traduit en NULL SQL if (sql_test_date($type) and preg_match('/^\w+\(/', $v)) { return $v; } if (sql_test_int($type)) { if (is_numeric($v) or (ctype_xdigit(substr($v, 2)) and $v[0] == '0' and $v[1] == 'x') ) { return $v; } // si pas numerique, forcer le intval else { return intval($v); } } return ("'" . addslashes($v) . "'"); } // Ces deux fonctions n'ont pas d'equivalent exact PostGres // et ne sont la que pour compatibilite avec les extensions de SPIP < 1.9.3 /** * Poser un verrou SQL local * * Changer de nom toutes les heures en cas de blocage MySQL (ca arrive) * * @deprecated Pas d'équivalence actuellement en dehors de MySQL * @see spip_release_lock() * * @param string $nom * Inutilisé. Le nom est calculé en fonction de la connexion principale * @param int $timeout * @return string|bool * - Nom du verrou si réussite, * - false sinon */ function spip_get_lock($nom, $timeout = 0) { define('_LOCK_TIME', intval(time() / 3600 - 316982)); $connexion = &$GLOBALS['connexions'][0]; $bd = $connexion['db']; $prefixe = $connexion['prefixe']; $nom = "$bd:$prefixe:$nom" . _LOCK_TIME; $connexion['last'] = $q = "SELECT GET_LOCK(" . _q($nom) . ", $timeout) AS n"; $q = @sql_fetch(mysql_query($q)); if (!$q) { spip_log("pas de lock sql pour $nom", _LOG_ERREUR); } return $q['n']; } /** * Relâcher un verrou SQL local * * @deprecated Pas d'équivalence actuellement en dehors de MySQL * @see spip_get_lock() * * @param string $nom * Inutilisé. Le nom est calculé en fonction de la connexion principale * @return string|bool * True si réussite, false sinon. */ function spip_release_lock($nom) { $connexion = &$GLOBALS['connexions'][0]; $bd = $connexion['db']; $prefixe = $connexion['prefixe']; $nom = "$bd:$prefixe:$nom" . _LOCK_TIME; $connexion['last'] = $q = "SELECT RELEASE_LOCK(" . _q($nom) . ")"; mysqli_query(_mysql_link(), $q); } /** * Teste si on a les fonctions MySQLi (pour l'install) * * @return bool * True si on a les fonctions, false sinon */ function spip_versions_mysql() { charger_php_extension('mysqli'); return function_exists('mysqli_query'); } /** * Tester si mysql ne veut pas du nom de la base dans les requêtes * * @param string $server_db * @return string * - chaîne vide si nom de la base utile * - chaîne : code compilé pour le faire désactiver par SPIP sinon */ function test_rappel_nom_base_mysql($server_db) { $GLOBALS['mysql_rappel_nom_base'] = true; sql_delete('spip_meta', "nom='mysql_rappel_nom_base'", $server_db); $ok = spip_query("INSERT INTO spip_meta (nom,valeur) VALUES ('mysql_rappel_nom_base', 'test')", $server_db); if ($ok) { sql_delete('spip_meta', "nom='mysql_rappel_nom_base'", $server_db); return ''; } else { $GLOBALS['mysql_rappel_nom_base'] = false; return "\$GLOBALS['mysql_rappel_nom_base'] = false; " . "/* echec de test_rappel_nom_base_mysql a l'installation. */\n"; } } /** * Teste si on peut changer les modes de MySQL * * @link http://dev.mysql.com/doc/refman/5.0/fr/server-sql-mode.html * * @param string $server_db Nom de la connexion * @return string * - chaîne vide si on ne peut pas appliquer de mode * - chaîne : code compilé pour l'indiquer le résultat du test à SPIP */ function test_sql_mode_mysql($server_db) { $res = sql_select("version() as v", '', '', '', '', '', '', $server_db); $row = sql_fetch($res, $server_db); if (version_compare($row['v'], '5.0.0', '>=')) { defined('_MYSQL_SET_SQL_MODE') || define('_MYSQL_SET_SQL_MODE', true); return "defined('_MYSQL_SET_SQL_MODE') || define('_MYSQL_SET_SQL_MODE',true);\n"; } return ''; } sqlite_generique.php 0000666 00000266324 15245543621 0010652 0 ustar 00 <?php /* *************************************************************************\ * SPIP, Systeme de publication pour l'internet * * * * Copyright (c) 2001-2019 * * Arnaud Martin, Antoine Pitrou, Philippe Riviere, Emmanuel Saint-James * * * * Ce programme est un logiciel libre distribue sous licence GNU/GPL. * * Pour plus de details voir le fichier COPYING.txt ou l'aide en ligne. * \***************************************************************************/ /** * Ce fichier contient les fonctions gérant * les instructions SQL pour Sqlite * * @package SPIP\Core\SQL\SQLite */ if (!defined('_ECRIRE_INC_VERSION')) { return; } // TODO: get/set_caracteres ? /* * regroupe le maximum de fonctions qui peuvent cohabiter * D'abord les fonctions d'abstractions de SPIP */ /** * Connecteur à une base SQLite * * @param string $addr * @param int $port * @param string $login * @param string $pass * @param string $db * @param string $prefixe * @param string $sqlite_version * @return array|bool */ function req_sqlite_dist($addr, $port, $login, $pass, $db = '', $prefixe = '', $sqlite_version = '') { static $last_connect = array(); // si provient de selectdb // un code pour etre sur que l'on vient de select_db() if (strpos($db, $code = '@selectdb@') !== false) { foreach (array('addr', 'port', 'login', 'pass', 'prefixe') as $a) { $$a = $last_connect[$a]; } $db = str_replace($code, '', $db); } /* * En sqlite, seule l'adresse du fichier est importante. * Ce sera $db le nom, * le path est $addr * (_DIR_DB si $addr est vide) */ _sqlite_init(); // determiner le dossier de la base : $addr ou _DIR_DB $f = _DIR_DB; if ($addr and strpos($addr, '/') !== false) { $f = rtrim($addr, '/') . '/'; } // un nom de base demande et impossible d'obtenir la base, on s'en va : // il faut que la base existe ou que le repertoire parent soit writable if ($db and !is_file($f .= $db . '.sqlite') and !is_writable(dirname($f))) { spip_log("base $f non trouvee ou droits en ecriture manquants", 'sqlite.' . _LOG_HS); return false; } // charger les modules sqlite au besoin if (!_sqlite_charger_version($sqlite_version)) { spip_log("Impossible de trouver/charger le module SQLite ($sqlite_version)!", 'sqlite.' . _LOG_HS); return false; } // chargement des constantes // il ne faut pas definir les constantes avant d'avoir charge les modules sqlite $define = "spip_sqlite" . $sqlite_version . "_constantes"; $define(); $ok = false; if (!$db) { // si pas de db -> // base temporaire tant qu'on ne connait pas son vrai nom // pour tester la connexion $db = "_sqlite" . $sqlite_version . "_install"; $tmp = _DIR_DB . $db . ".sqlite"; if ($sqlite_version == 3) { $ok = $link = new PDO("sqlite:$tmp"); } else { $ok = $link = sqlite_open($tmp, _SQLITE_CHMOD, $err); } } else { // Ouvrir (eventuellement creer la base) // si pas de version fourni, on essaie la 3, sinon la 2 if ($sqlite_version == 3) { $ok = $link = new PDO("sqlite:$f"); } else { $ok = $link = sqlite_open($f, _SQLITE_CHMOD, $err); } } if (!$ok) { $e = sqlite_last_error($db); spip_log("Impossible d'ouvrir la base SQLite($sqlite_version) $f : $e", 'sqlite.' . _LOG_HS); return false; } if ($link) { $last_connect = array( 'addr' => $addr, 'port' => $port, 'login' => $login, 'pass' => $pass, 'db' => $db, 'prefixe' => $prefixe, ); // etre sur qu'on definit bien les fonctions a chaque nouvelle connexion include_spip('req/sqlite_fonctions'); _sqlite_init_functions($link); } return array( 'db' => $db, 'prefixe' => $prefixe ? $prefixe : $db, 'link' => $link, 'total_requetes' => 0, ); } /** * Fonction de requete generale, munie d'une trace a la demande * * @param string $query * Requete a executer * @param string $serveur * Nom du connecteur * @param bool $requeter * Effectuer la requete ? * - true pour executer * - false pour retourner le texte de la requete * @return bool|SQLiteResult|string * Resultat de la requete */ function spip_sqlite_query($query, $serveur = '', $requeter = true) { #spip_log("spip_sqlite_query() > $query",'sqlite.'._LOG_DEBUG); #_sqlite_init(); // fait la premiere fois dans spip_sqlite $query = spip_sqlite::traduire_requete($query, $serveur); if (!$requeter) { return $query; } return spip_sqlite::executer_requete($query, $serveur); } /* ordre alphabetique pour les autres */ /** * Modifie une structure de table SQLite * * @param string $query Requête SQL (sans 'ALTER ') * @param string $serveur Nom de la connexion * @param bool $requeter inutilisé * @return bool * False si erreur dans l'exécution, true sinon */ function spip_sqlite_alter($query, $serveur = '', $requeter = true) { $query = spip_sqlite_query("ALTER $query", $serveur, false); // traduire la requete pour recuperer les bons noms de table $query = spip_sqlite::traduire_requete($query, $serveur); /* * la il faut faire les transformations * si ALTER TABLE x (DROP|CHANGE) y * * 1) recuperer "ALTER TABLE table " * 2) spliter les sous requetes (,) * 3) faire chaque requete independemment */ // 1 if (preg_match("/\s*(ALTER(\s*IGNORE)?\s*TABLE\s*([^\s]*))\s*(.*)?/is", $query, $regs)) { $debut = $regs[1]; $table = $regs[3]; $suite = $regs[4]; } else { spip_log("SQLite : Probleme de ALTER TABLE mal forme dans $query", 'sqlite.' . _LOG_ERREUR); return false; } // 2 // il faudrait une regexp pour eviter de spliter ADD PRIMARY KEY (colA, colB) // tout en cassant "ADD PRIMARY KEY (colA, colB), ADD INDEX (chose)"... en deux // ou revoir l'api de sql_alter en creant un // sql_alter_table($table,array($actions)); $todo = explode(',', $suite); // on remet les morceaux dechires ensembles... que c'est laid ! $todo2 = array(); $i = 0; $ouverte = false; while ($do = array_shift($todo)) { $todo2[$i] = isset($todo2[$i]) ? $todo2[$i] . "," . $do : $do; $o = (false !== strpos($do, "(")); $f = (false !== strpos($do, ")")); if ($o and !$f) { $ouverte = true; } elseif ($f) { $ouverte = false; } if (!$ouverte) { $i++; } } // 3 $resultats = array(); foreach ($todo2 as $do) { $do = trim($do); if (!preg_match('/(DROP PRIMARY KEY|DROP KEY|DROP INDEX|DROP COLUMN|DROP' . '|CHANGE COLUMN|CHANGE|MODIFY|RENAME TO|RENAME' . '|ADD PRIMARY KEY|ADD KEY|ADD INDEX|ADD UNIQUE KEY|ADD UNIQUE' . '|ADD COLUMN|ADD' . ')\s*([^\s]*)\s*(.*)?/i', $do, $matches) ) { spip_log("SQLite : Probleme de ALTER TABLE, utilisation non reconnue dans : $do \n(requete d'origine : $query)", 'sqlite.' . _LOG_ERREUR); return false; } $cle = strtoupper($matches[1]); $colonne_origine = $matches[2]; $colonne_destination = ''; $def = $matches[3]; // eluder une eventuelle clause before|after|first inutilisable $defr = rtrim(preg_replace('/(BEFORE|AFTER|FIRST)(.*)$/is', '', $def)); $defo = $defr; // garder la def d'origine pour certains cas // remplacer les definitions venant de mysql $defr = _sqlite_remplacements_definitions_table($defr); // reinjecter dans le do $do = str_replace($def, $defr, $do); $def = $defr; switch ($cle) { // suppression d'un index case 'DROP KEY': case 'DROP INDEX': $nom_index = $colonne_origine; spip_sqlite_drop_index($nom_index, $table, $serveur); break; // suppression d'une pk case 'DROP PRIMARY KEY': if (!_sqlite_modifier_table( $table, $colonne_origine, array('key' => array('PRIMARY KEY' => '')), $serveur) ) { return false; } break; // suppression d'une colonne case 'DROP COLUMN': case 'DROP': if (!_sqlite_modifier_table( $table, array($colonne_origine => ""), array(), $serveur) ) { return false; } break; case 'CHANGE COLUMN': case 'CHANGE': // recuperer le nom de la future colonne // on reprend la def d'origine car _sqlite_modifier_table va refaire la translation // en tenant compte de la cle primaire (ce qui est mieux) $def = trim($defo); $colonne_destination = substr($def, 0, strpos($def, ' ')); $def = substr($def, strlen($colonne_destination) + 1); if (!_sqlite_modifier_table( $table, array($colonne_origine => $colonne_destination), array('field' => array($colonne_destination => $def)), $serveur) ) { return false; } break; case 'MODIFY': // on reprend la def d'origine car _sqlite_modifier_table va refaire la translation // en tenant compte de la cle primaire (ce qui est mieux) if (!_sqlite_modifier_table( $table, $colonne_origine, array('field' => array($colonne_origine => $defo)), $serveur) ) { return false; } break; // pas geres en sqlite2 case 'RENAME': $do = "RENAME TO" . substr($do, 6); case 'RENAME TO': if (_sqlite_is_version(3, '', $serveur)) { if (!spip_sqlite::executer_requete("$debut $do", $serveur)) { spip_log("SQLite : Erreur ALTER TABLE / RENAME : $query", 'sqlite.' . _LOG_ERREUR); return false; } // artillerie lourde pour sqlite2 ! } else { $table_dest = trim(substr($do, 9)); if (!_sqlite_modifier_table(array($table => $table_dest), '', array(), $serveur)) { spip_log("SQLite : Erreur ALTER TABLE / RENAME : $query", 'sqlite.' . _LOG_ERREUR); return false; } } break; // ajout d'une pk case 'ADD PRIMARY KEY': $pk = trim(substr($do, 16)); $pk = ($pk[0] == '(') ? substr($pk, 1, -1) : $pk; if (!_sqlite_modifier_table( $table, $colonne_origine, array('key' => array('PRIMARY KEY' => $pk)), $serveur) ) { return false; } break; // ajout d'un index case 'ADD UNIQUE KEY': case 'ADD UNIQUE': $unique = true; case 'ADD INDEX': case 'ADD KEY': if (!isset($unique)) { $unique = false; } // peut etre "(colonne)" ou "nom_index (colonnes)" // bug potentiel si qqn met "(colonne, colonne)" // // nom_index (colonnes) if ($def) { $colonnes = substr($def, 1, -1); $nom_index = $colonne_origine; } else { // (colonne) if ($colonne_origine[0] == "(") { $colonnes = substr($colonne_origine, 1, -1); if (false !== strpos(",", $colonnes)) { spip_log(_LOG_GRAVITE_ERREUR, "SQLite : Erreur, impossible de creer un index sur plusieurs colonnes" . " sans qu'il ait de nom ($table, ($colonnes))", 'sqlite'); break; } else { $nom_index = $colonnes; } } // nom_index else { $nom_index = $colonnes = $colonne_origine; } } spip_sqlite_create_index($nom_index, $table, $colonnes, $unique, $serveur); break; // pas geres en sqlite2 case 'ADD COLUMN': $do = "ADD" . substr($do, 10); case 'ADD': default: if (_sqlite_is_version(3, '', $serveur) and !preg_match(',primary\s+key,i', $do)) { if (!spip_sqlite::executer_requete("$debut $do", $serveur)) { spip_log("SQLite : Erreur ALTER TABLE / ADD : $query", 'sqlite.' . _LOG_ERREUR); return false; } break; } // artillerie lourde pour sqlite2 ! // ou si la colonne est aussi primary key // cas du add id_truc int primary key // ajout d'une colonne qui passe en primary key directe else { $def = trim(substr($do, 3)); $colonne_ajoutee = substr($def, 0, strpos($def, ' ')); $def = substr($def, strlen($colonne_ajoutee) + 1); $opts = array(); if (preg_match(',primary\s+key,i', $def)) { $opts['key'] = array('PRIMARY KEY' => $colonne_ajoutee); $def = preg_replace(',primary\s+key,i', '', $def); } $opts['field'] = array($colonne_ajoutee => $def); if (!_sqlite_modifier_table($table, array($colonne_ajoutee), $opts, $serveur)) { spip_log("SQLite : Erreur ALTER TABLE / ADD : $query", 'sqlite.' . _LOG_ERREUR); return false; } } break; } // tout est bon, ouf ! spip_log("SQLite ($serveur) : Changements OK : $debut $do", 'sqlite.' . _LOG_INFO); } spip_log("SQLite ($serveur) : fin ALTER TABLE OK !", 'sqlite.' . _LOG_INFO); return true; } /** * Crée une table SQL * * Crée une table SQL nommee `$nom` à partir des 2 tableaux `$champs` et `$cles` * * @note Le nom des caches doit être inferieur à 64 caractères * * @param string $nom Nom de la table SQL * @param array $champs Couples (champ => description SQL) * @param array $cles Couples (type de clé => champ(s) de la clé) * @param bool $autoinc True pour ajouter un auto-incrément sur la Primary Key * @param bool $temporary True pour créer une table temporaire * @param string $serveur Nom de la connexion * @param bool $requeter Exécuter la requête, sinon la retourner * @return array|null|resource|string * - string Texte de la requête si demandée * - true si la requête réussie, false sinon. */ function spip_sqlite_create( $nom, $champs, $cles, $autoinc = false, $temporary = false, $serveur = '', $requeter = true ) { $query = _sqlite_requete_create($nom, $champs, $cles, $autoinc, $temporary, $ifnotexists = true, $serveur, $requeter); if (!$query) { return false; } $res = spip_sqlite_query($query, $serveur, $requeter); // SQLite ne cree pas les KEY sur les requetes CREATE TABLE // il faut donc les faire creer ensuite if (!$requeter) { return $res; } $ok = $res ? true : false; if ($ok) { foreach ($cles as $k => $v) { if (preg_match(',^(KEY|UNIQUE)\s,i', $k, $m)) { $index = trim(substr($k, strlen($m[1]))); $unique = (strlen($m[1]) > 3); $ok &= spip_sqlite_create_index($index, $nom, $v, $unique, $serveur); } } } return $ok ? true : false; } /** * Crée une base de données SQLite * * @param string $nom Nom de la base (sans l'extension de fichier) * @param string $serveur Nom de la connexion * @param string $option Options * * @return bool true si la base est créee. **/ function spip_sqlite_create_base($nom, $serveur = '', $option = true) { $f = $nom . '.sqlite'; if (strpos($nom, "/") === false) { $f = _DIR_DB . $f; } if (_sqlite_is_version(2, '', $serveur)) { $ok = sqlite_open($f, _SQLITE_CHMOD, $err); } else { $ok = new PDO("sqlite:$f"); } if ($ok) { unset($ok); return true; } unset($ok); return false; } /** * Crée une vue SQL nommée `$nom` * * @param string $nom * Nom de la vue a creer * @param string $query_select * Texte de la requête de sélection servant de base à la vue * @param string $serveur * Nom du connecteur * @param bool $requeter * Effectuer la requete, sinon la retourner * @return bool|SQLiteResult|string * - true si la vue est créée * - false si erreur ou si la vue existe déja * - string texte de la requête si $requeter vaut false */ function spip_sqlite_create_view($nom, $query_select, $serveur = '', $requeter = true) { if (!$query_select) { return false; } // vue deja presente if (sql_showtable($nom, false, $serveur)) { spip_log("Echec creation d'une vue sql ($nom) car celle-ci existe deja (serveur:$serveur)", 'sqlite.' . _LOG_ERREUR); return false; } $query = "CREATE VIEW $nom AS " . $query_select; return spip_sqlite_query($query, $serveur, $requeter); } /** * Fonction de création d'un INDEX * * @param string $nom * Nom de l'index * @param string $table * Table SQL de l'index * @param string|array $champs * Liste de champs sur lesquels s'applique l'index * @param string|bool $unique * Créer un index UNIQUE ? * @param string $serveur * Nom de la connexion sql utilisee * @param bool $requeter * true pour executer la requête ou false pour retourner le texte de la requête * @return bool|string * string : requête, false si erreur, true sinon. */ function spip_sqlite_create_index($nom, $table, $champs, $unique = '', $serveur = '', $requeter = true) { if (!($nom or $table or $champs)) { spip_log("Champ manquant pour creer un index sqlite ($nom, $table, (" . join(',', $champs) . "))", 'sqlite.' . _LOG_ERREUR); return false; } // SQLite ne differentie pas noms des index en fonction des tables // il faut donc creer des noms uniques d'index pour une base sqlite $nom = $table . '_' . $nom; // enlever d'eventuelles parentheses deja presentes sur champs if (!is_array($champs)) { if ($champs[0] == "(") { $champs = substr($champs, 1, -1); } $champs = array($champs); // supprimer l'info de longueur d'index mysql en fin de champ $champs = preg_replace(",\(\d+\)$,", "", $champs); } $ifnotexists = ""; $version = spip_sqlite_fetch(spip_sqlite_query("select sqlite_version() AS sqlite_version", $serveur), '', $serveur); if (!function_exists('spip_version_compare')) { include_spip('plugins/installer'); } if ($version and spip_version_compare($version['sqlite_version'], '3.3.0', '>=')) { $ifnotexists = ' IF NOT EXISTS'; } else { /* simuler le IF EXISTS - version 2 et sqlite < 3.3a */ $a = spip_sqlite_showtable($table, $serveur); if (isset($a['key']['KEY ' . $nom])) { return true; } } $query = "CREATE " . ($unique ? "UNIQUE " : "") . "INDEX$ifnotexists $nom ON $table (" . join(',', $champs) . ")"; $res = spip_sqlite_query($query, $serveur, $requeter); if (!$requeter) { return $res; } if ($res) { return true; } else { return false; } } /** * Retourne le nombre de lignes d’une ressource de sélection obtenue * avec `sql_select()` * * En PDO/sqlite3, il faut calculer le count par une requete count(*) * pour les resultats de SELECT * cela est fait sans spip_sqlite_query() * * @param Ressource|Object $r Ressource de résultat * @param string $serveur Nom de la connexion * @param bool $requeter Inutilisé * @return int Nombre de lignes */ function spip_sqlite_count($r, $serveur = '', $requeter = true) { if (!$r) { return 0; } if (_sqlite_is_version(3, '', $serveur)) { // select ou autre (insert, update,...) ? // (link,requete) a compter if (is_array($r->spipSqliteRowCount)) { list($link, $query) = $r->spipSqliteRowCount; // amelioration possible a tester intensivement : pas de order by pour compter ! // $query = preg_replace(",ORDER BY .+(LIMIT\s|HAVING\s|GROUP BY\s|$),Uims","\\1",$query); $query = "SELECT count(*) as zzzzsqlitecount FROM ($query)"; $l = $link->query($query); $i = 0; if ($l and $z = $l->fetch()) { $i = $z['zzzzsqlitecount']; } $r->spipSqliteRowCount = $i; } if (isset($r->spipSqliteRowCount)) { // Ce compte est faux s'il y a des limit dans la requete :( // il retourne le nombre d'enregistrements sans le limit return $r->spipSqliteRowCount; } else { return $r->rowCount(); } } else { return sqlite_num_rows($r); } } /** * Retourne le nombre de lignes d'une sélection * * @param array|string $from Tables à consulter (From) * @param array|string $where Conditions a remplir (Where) * @param array|string $groupby Critère de regroupement (Group by) * @param array $having Tableau des des post-conditions à remplir (Having) * @param string $serveur Nom de la connexion * @param bool $requeter Exécuter la requête, sinon la retourner * @return int|bool|string * - String Texte de la requête si demandé * - int Nombre de lignes * - false si la requête a échouée **/ function spip_sqlite_countsel( $from = array(), $where = array(), $groupby = '', $having = array(), $serveur = '', $requeter = true ) { $c = !$groupby ? '*' : ('DISTINCT ' . (is_string($groupby) ? $groupby : join(',', $groupby))); $r = spip_sqlite_select("COUNT($c)", $from, $where, '', '', '', $having, $serveur, $requeter); if ((is_resource($r) or is_object($r)) && $requeter) { // ressource : sqlite2, object : sqlite3 if (_sqlite_is_version(3, '', $serveur)) { list($r) = spip_sqlite_fetch($r, SPIP_SQLITE3_NUM, $serveur); } else { list($r) = spip_sqlite_fetch($r, SPIP_SQLITE2_NUM, $serveur); } } return $r; } /** * Supprime des enregistrements d'une table * * @param string $table Nom de la table SQL * @param string|array $where Conditions à vérifier * @param string $serveur Nom du connecteur * @param bool $requeter Exécuter la requête, sinon la retourner * @return bool|string * - int : nombre de suppressions réalisées, * - Texte de la requête si demandé, * - False en cas d'erreur. **/ function spip_sqlite_delete($table, $where = '', $serveur = '', $requeter = true) { $res = spip_sqlite_query( _sqlite_calculer_expression('DELETE FROM', $table, ',') . _sqlite_calculer_expression('WHERE', $where), $serveur, $requeter); // renvoyer la requete inerte si demandee if (!$requeter) { return $res; } if ($res) { $link = _sqlite_link($serveur); if (_sqlite_is_version(3, $link)) { return $res->rowCount(); } else { return sqlite_changes($link); } } else { return false; } } /** * Supprime une table SQL * * @param string $table Nom de la table SQL * @param string $exist True pour ajouter un test d'existence avant de supprimer * @param string $serveur Nom de la connexion * @param bool $requeter Exécuter la requête, sinon la retourner * @return bool|string * - string Texte de la requête si demandé * - true si la requête a réussie, false sinon */ function spip_sqlite_drop_table($table, $exist = '', $serveur = '', $requeter = true) { if ($exist) { $exist = " IF EXISTS"; } /* simuler le IF EXISTS - version 2 */ if ($exist && _sqlite_is_version(2, '', $serveur)) { $a = spip_sqlite_showtable($table, $serveur); if (!$a) { return true; } $exist = ''; } if (spip_sqlite_query("DROP TABLE$exist $table", $serveur, $requeter)) { return true; } else { return false; } } /** * Supprime une vue SQL * * @param string $view Nom de la vue SQL * @param string $exist True pour ajouter un test d'existence avant de supprimer * @param string $serveur Nom de la connexion * @param bool $requeter Exécuter la requête, sinon la retourner * @return bool|string * - string Texte de la requête si demandé * - true si la requête a réussie, false sinon */ function spip_sqlite_drop_view($view, $exist = '', $serveur = '', $requeter = true) { if ($exist) { $exist = " IF EXISTS"; } /* simuler le IF EXISTS - version 2 */ if ($exist && _sqlite_is_version(2, '', $serveur)) { $a = spip_sqlite_showtable($view, $serveur); if (!$a) { return true; } $exist = ''; } return spip_sqlite_query("DROP VIEW$exist $view", $serveur, $requeter); } /** * Fonction de suppression d'un INDEX * * @param string $nom : nom de l'index * @param string $table : table sql de l'index * @param string $serveur : nom de la connexion sql utilisee * @param bool $requeter : true pour executer la requête ou false pour retourner le texte de la requête * * @return bool ou requete */ function spip_sqlite_drop_index($nom, $table, $serveur = '', $requeter = true) { if (!($nom or $table)) { spip_log("Champ manquant pour supprimer un index sqlite ($nom, $table)", 'sqlite.' . _LOG_ERREUR); return false; } // SQLite ne differentie pas noms des index en fonction des tables // il faut donc creer des noms uniques d'index pour une base sqlite $index = $table . '_' . $nom; $exist = " IF EXISTS"; /* simuler le IF EXISTS - version 2 */ if (_sqlite_is_version(2, '', $serveur)) { $a = spip_sqlite_showtable($table, $serveur); if (!isset($a['key']['KEY ' . $nom])) { return true; } $exist = ''; } $query = "DROP INDEX$exist $index"; return spip_sqlite_query($query, $serveur, $requeter); } /** * Retourne la dernière erreur generée * * @uses sql_error_backtrace() * * @param string $query * Requête qui était exécutée * @param string $serveur * Nom de la connexion * @return string * Erreur eventuelle **/ function spip_sqlite_error($query = '', $serveur = '') { $link = _sqlite_link($serveur); if (_sqlite_is_version(3, $link)) { $errs = $link->errorInfo(); /* $errs[0] numero SQLState ('HY000' souvent lors d'une erreur) http://www.easysoft.com/developer/interfaces/odbc/sqlstate_status_return_codes.html $errs[1] numéro d'erreur SQLite (souvent 1 lors d'une erreur) http://www.sqlite.org/c3ref/c_abort.html $errs[2] Le texte du message d'erreur */ $s = ''; if (ltrim($errs[0], '0')) { // 00000 si pas d'erreur $s = "$errs[2]"; } } elseif ($link) { $s = sqlite_error_string(sqlite_last_error($link)); } else { $s = ": aucune ressource sqlite (link)"; } if ($s) { $trace = debug_backtrace(); if ($trace[0]['function'] != "spip_mysql_error") { spip_log("$s - $query - " . sql_error_backtrace(), 'sqlite.' . _LOG_ERREUR); } } return $s; } /** * Retourne le numero de la dernière erreur SQL * * Le numéro (en sqlite3/pdo) est un retour ODBC tel que (très souvent) HY000 * http://www.easysoft.com/developer/interfaces/odbc/sqlstate_status_return_codes.html * * @param string $serveur * nom de la connexion * @return int|string * 0 pas d'erreur * 1 ou autre erreur (en sqlite 2) * 'HY000/1' : numéro de l'erreur SQLState / numéro d'erreur interne SQLite (en sqlite 3) **/ function spip_sqlite_errno($serveur = '') { $link = _sqlite_link($serveur); if (_sqlite_is_version(3, $link)) { $t = $link->errorInfo(); $s = ltrim($t[0], '0'); // 00000 si pas d'erreur if ($s) { $s .= ' / ' . $t[1]; } // ajoute l'erreur du moteur SQLite } elseif ($link) { $s = sqlite_last_error($link); } else { $s = ": aucune ressource sqlite (link)"; } if ($s) { spip_log("Erreur sqlite $s", 'sqlite.' . _LOG_ERREUR); } return $s ? $s : 0; } /** * Retourne une explication de requête (Explain) SQLite * * @param string $query Texte de la requête * @param string $serveur Nom de la connexion * @param bool $requeter Exécuter la requête, sinon la retourner * @return array|string|bool * - array : Tableau de l'explication * - string si on retourne le texte de la requête * - false si on a pas pu avoir d'explication */ function spip_sqlite_explain($query, $serveur = '', $requeter = true) { if (strpos(ltrim($query), 'SELECT') !== 0) { return array(); } $query = spip_sqlite::traduire_requete($query, $serveur); $query = 'EXPLAIN ' . $query; if (!$requeter) { return $query; } // on ne trace pas ces requetes, sinon on obtient un tracage sans fin... $r = spip_sqlite::executer_requete($query, $serveur, false); return $r ? spip_sqlite_fetch($r, null, $serveur) : false; // hum ? etrange ca... a verifier } /** * Rècupère une ligne de résultat * * Récupère la ligne suivante d'une ressource de résultat * * @param Ressource $r Ressource de résultat (issu de sql_select) * @param string $t Structure de résultat attendu (défaut ASSOC) * @param string $serveur Nom de la connexion * @param bool $requeter Inutilisé * @return array Ligne de résultat */ function spip_sqlite_fetch($r, $t = '', $serveur = '', $requeter = true) { $link = _sqlite_link($serveur); $is_v3 = _sqlite_is_version(3, $link); if (!$t) { $t = ($is_v3 ? SPIP_SQLITE3_ASSOC : SPIP_SQLITE2_ASSOC); } $retour = false; if ($r) { $retour = ($is_v3 ? $r->fetch($t) : sqlite_fetch_array($r, $t)); } // les version 2 et 3 parfois renvoie des 'table.titre' au lieu de 'titre' tout court ! pff ! // suppression de 'table.' pour toutes les cles (c'est un peu violent !) // c'est couteux : on ne verifie que la premiere ligne pour voir si on le fait ou non if ($retour and strpos(implode('', array_keys($retour)), '.') !== false ) { foreach ($retour as $cle => $val) { if (($pos = strpos($cle, '.')) !== false) { $retour[substr($cle, $pos + 1)] = &$retour[$cle]; unset($retour[$cle]); } } } return $retour; } /** * Place le pointeur de résultat sur la position indiquée * * @param Ressource $r Ressource de résultat * @param int $row_number Position. Déplacer le pointeur à cette ligne * @param string $serveur Nom de la connexion * @param bool $requeter Inutilisé * @return bool True si déplacement réussi, false sinon. **/ function spip_sqlite_seek($r, $row_number, $serveur = '', $requeter = true) { if ($r) { $link = _sqlite_link($serveur); if (_sqlite_is_version(3, $link)) { // encore un truc de bien fichu : PDO ne PEUT PAS faire de seek ou de rewind... // je me demande si pour sqlite 3 il ne faudrait pas mieux utiliser // les nouvelles fonctions sqlite3_xx (mais encore moins presentes...) return false; } else { return sqlite_seek($r, $row_number); } } } /** * Libère une ressource de résultat * * Indique à SQLite de libérer de sa mémoire la ressoucre de résultat indiquée * car on n'a plus besoin de l'utiliser. * * @param Ressource|Object $r Ressource de résultat * @param string $serveur Nom de la connexion * @param bool $requeter Inutilisé * @return bool True si réussi */ function spip_sqlite_free(&$r, $serveur = '', $requeter = true) { unset($r); return true; //return sqlite_free_result($r); } /** * Teste si le charset indiqué est disponible sur le serveur SQL (aucune action ici) * * Cette fonction n'a aucune action actuellement * * @param array|string $charset Nom du charset à tester. * @param string $serveur Nom de la connexion * @param bool $requeter inutilisé * @return void */ function spip_sqlite_get_charset($charset = array(), $serveur = '', $requeter = true) { //$c = !$charset ? '' : (" LIKE "._q($charset['charset'])); //return spip_sqlite_fetch(sqlite_query(_sqlite_link($serveur), "SHOW CHARACTER SET$c"), NULL, $serveur); } /** * Prépare une chaîne hexadécimale * * Par exemple : FF ==> 255 en SQLite * * @param string $v * Chaine hexadecimale * @return string * Valeur hexadécimale pour SQLite **/ function spip_sqlite_hex($v) { return hexdec($v); } /** * Retourne une expression IN pour le gestionnaire de base de données * * IN (...) est limité à 255 éléments, d'où cette fonction assistante * * @param string $val * Colonne SQL sur laquelle appliquer le test * @param string|array $valeurs * Liste des valeurs possibles (séparés par des virgules si string) * @param string $not * - '' sélectionne les éléments correspondant aux valeurs * - 'NOT' inverse en sélectionnant les éléments ne correspondant pas aux valeurs * @param string $serveur * Nom du connecteur * @param bool $requeter * Inutilisé * @return string * Expression de requête SQL **/ function spip_sqlite_in($val, $valeurs, $not = '', $serveur = '', $requeter = true) { $n = $i = 0; $in_sql = ""; while ($n = strpos($valeurs, ',', $n + 1)) { if ((++$i) >= 255) { $in_sql .= "($val $not IN (" . substr($valeurs, 0, $n) . "))\n" . ($not ? "AND\t" : "OR\t"); $valeurs = substr($valeurs, $n + 1); $i = $n = 0; } } $in_sql .= "($val $not IN ($valeurs))"; return "($in_sql)"; } /** * Insère une ligne dans une table * * @param string $table * Nom de la table SQL * @param string $champs * Liste des colonnes impactées, * @param string $valeurs * Liste des valeurs, * @param array $desc * Tableau de description des colonnes de la table SQL utilisée * (il sera calculé si nécessaire s'il n'est pas transmis). * @param string $serveur * Nom du connecteur * @param bool $requeter * Exécuter la requête, sinon la retourner * @return bool|string|int|array * - int|true identifiant de l'élément inséré (si possible), ou true, si réussite * - Texte de la requête si demandé, * - False en cas d'erreur, * - Tableau de description de la requête et du temps d'exécution, si var_profile activé **/ function spip_sqlite_insert($table, $champs, $valeurs, $desc = array(), $serveur = '', $requeter = true) { $query = "INSERT INTO $table " . ($champs ? "$champs VALUES $valeurs" : "DEFAULT VALUES"); if ($r = spip_sqlite_query($query, $serveur, $requeter)) { if (!$requeter) { return $r; } $nb = spip_sqlite::last_insert_id($serveur); } else { $nb = false; } $err = spip_sqlite_error($query, $serveur); // cas particulier : ne pas substituer la reponse spip_sqlite_query si on est en profilage return isset($_GET['var_profile']) ? $r : $nb; } /** * Insère une ligne dans une table, en protégeant chaque valeur * * @param string $table * Nom de la table SQL * @param string $couples * Couples (colonne => valeur) * @param array $desc * Tableau de description des colonnes de la table SQL utilisée * (il sera calculé si nécessaire s'il n'est pas transmis). * @param string $serveur * Nom du connecteur * @param bool $requeter * Exécuter la requête, sinon la retourner * @return bool|string|int|array * - int|true identifiant de l'élément inséré (si possible), ou true, si réussite * - Texte de la requête si demandé, * - False en cas d'erreur, * - Tableau de description de la requête et du temps d'exécution, si var_profile activé **/ function spip_sqlite_insertq($table, $couples = array(), $desc = array(), $serveur = '', $requeter = true) { if (!$desc) { $desc = description_table($table, $serveur); } if (!$desc) { die("$table insertion sans description"); } $fields = isset($desc['field']) ? $desc['field'] : array(); foreach ($couples as $champ => $val) { $couples[$champ] = _sqlite_calculer_cite($val, $fields[$champ]); } // recherche de champs 'timestamp' pour mise a jour auto de ceux-ci $couples = _sqlite_ajouter_champs_timestamp($table, $couples, $desc, $serveur); $cles = $valeurs = ""; if (count($couples)) { $cles = "(" . join(',', array_keys($couples)) . ")"; $valeurs = "(" . join(',', $couples) . ")"; } return spip_sqlite_insert($table, $cles, $valeurs, $desc, $serveur, $requeter); } /** * Insère plusieurs lignes d'un coup dans une table * * @param string $table * Nom de la table SQL * @param array $tab_couples * Tableau de tableaux associatifs (colonne => valeur) * @param array $desc * Tableau de description des colonnes de la table SQL utilisée * (il sera calculé si nécessaire s'il n'est pas transmis). * @param string $serveur * Nom du connecteur * @param bool $requeter * Exécuter la requête, sinon la retourner * @return bool|string * - True en cas de succès, * - Texte de la requête si demandé, * - False en cas d'erreur. **/ function spip_sqlite_insertq_multi($table, $tab_couples = array(), $desc = array(), $serveur = '', $requeter = true) { if (!$desc) { $desc = description_table($table, $serveur); } if (!$desc) { die("$table insertion sans description"); } if (!isset($desc['field'])) { $desc['field'] = array(); } // recuperer les champs 'timestamp' pour mise a jour auto de ceux-ci $maj = _sqlite_ajouter_champs_timestamp($table, array(), $desc, $serveur); // seul le nom de la table est a traduire ici : // le faire une seule fois au debut $query_start = "INSERT INTO $table "; $query_start = spip_sqlite::traduire_requete($query_start, $serveur); // ouvrir une transaction if ($requeter) { spip_sqlite::demarrer_transaction($serveur); } while ($couples = array_shift($tab_couples)) { foreach ($couples as $champ => $val) { $couples[$champ] = _sqlite_calculer_cite($val, $desc['field'][$champ]); } // inserer les champs timestamp par defaut $couples = array_merge($maj, $couples); $champs = $valeurs = ""; if (count($couples)) { $champs = "(" . join(',', array_keys($couples)) . ")"; $valeurs = "(" . join(',', $couples) . ")"; $query = $query_start . "$champs VALUES $valeurs"; } else { $query = $query_start . "DEFAULT VALUES"; } if ($requeter) { $retour = spip_sqlite::executer_requete($query, $serveur); } // sur le dernier couple uniquement if (!count($tab_couples)) { $nb = 0; if ($requeter) { $nb = spip_sqlite::last_insert_id($serveur); } else { return $query; } } $err = spip_sqlite_error($query, $serveur); } if ($requeter) { spip_sqlite::finir_transaction($serveur); } // renvoie le dernier id d'autoincrement ajoute // cas particulier : ne pas substituer la reponse spip_sqlite_query si on est en profilage return isset($_GET['var_profile']) ? $retour : $nb; } /** * Retourne si le moteur SQL préfère utiliser des transactions. * * @param string $serveur * Nom du connecteur * @param bool $requeter * Inutilisé * @return bool * Toujours true. **/ function spip_sqlite_preferer_transaction($serveur = '', $requeter = true) { return true; } /** * Démarre une transaction * * Pratique pour des sql_updateq() dans un foreach, * parfois 100* plus rapide s'ils sont nombreux en sqlite ! * * @param string $serveur * Nom du connecteur * @param bool $requeter * true pour exécuter la requête ou false pour retourner le texte de la requête * @return bool|string * string si texte de la requête demandé, true sinon **/ function spip_sqlite_demarrer_transaction($serveur = '', $requeter = true) { if (!$requeter) { return "BEGIN TRANSACTION"; } spip_sqlite::demarrer_transaction($serveur); return true; } /** * Clôture une transaction * * @param string $serveur * Nom du connecteur * @param bool $requeter * true pour exécuter la requête ou false pour retourner le texte de la requête * @return bool|string * string si texte de la requête demandé, true sinon **/ function spip_sqlite_terminer_transaction($serveur = '', $requeter = true) { if (!$requeter) { return "COMMIT"; } spip_sqlite::finir_transaction($serveur); return true; } /** * Liste les bases de données disponibles * * @param string $serveur * Nom du connecteur * @param bool $requeter * Inutilisé * @return array * Liste des noms de bases **/ function spip_sqlite_listdbs($serveur = '', $requeter = true) { _sqlite_init(); if (!is_dir($d = substr(_DIR_DB, 0, -1))) { return array(); } include_spip('inc/flock'); $bases = preg_files($d, $pattern = '(.*)\.sqlite$'); $bds = array(); foreach ($bases as $b) { // pas de bases commencant pas sqlite // (on s'en sert pour l'installation pour simuler la presence d'un serveur) // les bases sont de la forme _sqliteX_tmp_spip_install.sqlite if (strpos($b, '_sqlite')) { continue; } $bds[] = preg_replace(";.*/$pattern;iS", '$1', $b); } return $bds; } /** * Retourne l'instruction SQL pour obtenir le texte d'un champ contenant * une balise `<multi>` dans la langue indiquée * * Cette sélection est mise dans l'alias `multi` (instruction AS multi). * * @param string $objet Colonne ayant le texte * @param string $lang Langue à extraire * @return string Texte de sélection pour la requête */ function spip_sqlite_multi($objet, $lang) { $r = "EXTRAIRE_MULTI(" . $objet . ", '" . $lang . "') AS multi"; return $r; } /** * Optimise une table SQL * * @note * Sqlite optimise TOUT un fichier sinon rien. * On évite donc 2 traitements sur la même base dans un hit. * * @param $table nom de la table a optimiser * @param $serveur nom de la connexion * @param $requeter effectuer la requete ? sinon retourner son code * @return bool|string true / false / requete **/ function spip_sqlite_optimize($table, $serveur = '', $requeter = true) { static $do = false; if ($requeter and $do) { return true; } if ($requeter) { $do = true; } return spip_sqlite_query("VACUUM", $serveur, $requeter); } /** * Échapper une valeur selon son type * mais pour SQLite avec ses spécificités * * @param string|array|number $v * Texte, nombre ou tableau à échapper * @param string $type * Description du type attendu * (par exemple description SQL de la colonne recevant la donnée) * @return string|number * Donnée prête à être utilisée par le gestionnaire SQL */ function spip_sqlite_quote($v, $type = '') { if (!is_array($v)) { return _sqlite_calculer_cite($v, $type); } // si c'est un tableau, le parcourir en propageant le type foreach ($v as $k => $r) { $v[$k] = spip_sqlite_quote($r, $type); } return join(",", $v); } /** * Tester si une date est proche de la valeur d'un champ * * @param string $champ * Nom du champ a tester * @param int $interval * Valeur de l'intervalle : -1, 4, ... * @param string $unite * Utité utilisée (DAY, MONTH, YEAR, ...) * @return string * Expression SQL **/ function spip_sqlite_date_proche($champ, $interval, $unite) { $op = (($interval <= 0) ? '>' : '<'); return "($champ $op datetime('" . date("Y-m-d H:i:s") . "', '$interval $unite'))"; } /** * Répare une table SQL * * Il n'y a pas de fonction native repair dans sqlite, mais on profite * pour vérifier que tous les champs (text|char) ont bien une clause DEFAULT * * @param string $table Nom de la table SQL * @param string $serveur Nom de la connexion * @param bool $requeter Exécuter la requête, sinon la retourner * @return string[] * Tableau avec clé 0 pouvant avoir " OK " ou " ERROR " indiquant * l'état de la table après la réparation */ function spip_sqlite_repair($table, $serveur = '', $requeter = true) { if ($desc = spip_sqlite_showtable($table, $serveur) and isset($desc['field']) and is_array($desc['field']) ) { foreach ($desc['field'] as $c => $d) { if (preg_match(",^(tinytext|mediumtext|text|longtext|varchar|char),i", $d) and stripos($d, "NOT NULL") !== false and stripos($d, "DEFAULT") === false /* pas touche aux cles primaires */ and (!isset($desc['key']['PRIMARY KEY']) or $desc['key']['PRIMARY KEY'] !== $c) ) { spip_sqlite_alter($q = "TABLE $table CHANGE $c $c $d DEFAULT ''", $serveur); spip_log("ALTER $q", "repair" . _LOG_INFO_IMPORTANTE); } if (preg_match(",^(INTEGER),i", $d) and stripos($d, "NOT NULL") !== false and stripos($d, "DEFAULT") === false /* pas touche aux cles primaires */ and (!isset($desc['key']['PRIMARY KEY']) or $desc['key']['PRIMARY KEY'] !== $c) ) { spip_sqlite_alter($q = "TABLE $table CHANGE $c $c $d DEFAULT '0'", $serveur); spip_log("ALTER $q", "repair" . _LOG_INFO_IMPORTANTE); } if (preg_match(",^(datetime),i", $d) and stripos($d, "NOT NULL") !== false and stripos($d, "DEFAULT") === false /* pas touche aux cles primaires */ and (!isset($desc['key']['PRIMARY KEY']) or $desc['key']['PRIMARY KEY'] !== $c) ) { spip_sqlite_alter($q = "TABLE $table CHANGE $c $c $d DEFAULT '0000-00-00 00:00:00'", $serveur); spip_log("ALTER $q", "repair" . _LOG_INFO_IMPORTANTE); } } return array(" OK "); } return array(" ERROR "); } /** * Insère où met à jour une entrée d’une table SQL * * La clé ou les cles primaires doivent être présentes dans les données insérés. * La fonction effectue une protection automatique des données. * * Préférer à cette fonction updateq ou insertq. * * @param string $table * Nom de la table SQL * @param array $couples * Couples colonne / valeur à modifier, * @param array $desc * Tableau de description des colonnes de la table SQL utilisée * (il sera calculé si nécessaire s'il n'est pas transmis). * @param string $serveur * Nom du connecteur * @param bool $requeter * Exécuter la requête, sinon la retourner * @return bool|string * - true si réussite * - Texte de la requête si demandé, * - False en cas d'erreur. **/ function spip_sqlite_replace($table, $couples, $desc = array(), $serveur = '', $requeter = true) { if (!$desc) { $desc = description_table($table, $serveur); } if (!$desc) { die("$table insertion sans description"); } $fields = isset($desc['field']) ? $desc['field'] : array(); foreach ($couples as $champ => $val) { $couples[$champ] = _sqlite_calculer_cite($val, $fields[$champ]); } // recherche de champs 'timestamp' pour mise a jour auto de ceux-ci $couples = _sqlite_ajouter_champs_timestamp($table, $couples, $desc, $serveur); return spip_sqlite_query("REPLACE INTO $table (" . join(',', array_keys($couples)) . ') VALUES (' . join(',', $couples) . ')', $serveur); } /** * Insère où met à jour des entrées d’une table SQL * * La clé ou les cles primaires doivent être présentes dans les données insérés. * La fonction effectue une protection automatique des données. * * Préférez insertq_multi et sql_updateq * * @param string $table * Nom de la table SQL * @param array $tab_couples * Tableau de tableau (colonne / valeur à modifier), * @param array $desc * Tableau de description des colonnes de la table SQL utilisée * (il sera calculé si nécessaire s'il n'est pas transmis). * @param string $serveur * Nom du connecteur * @param bool $requeter * Exécuter la requête, sinon la retourner * @return bool|string * - true si réussite * - Texte de la requête si demandé, * - False en cas d'erreur. **/ function spip_sqlite_replace_multi($table, $tab_couples, $desc = array(), $serveur = '', $requeter = true) { // boucler pour trainter chaque requete independemment foreach ($tab_couples as $couples) { $retour = spip_sqlite_replace($table, $couples, $desc, $serveur, $requeter); } // renvoie le dernier id return $retour; } /** * Exécute une requête de sélection avec SQLite * * Instance de sql_select (voir ses specs). * * @see sql_select() * * @param string|array $select Champs sélectionnés * @param string|array $from Tables sélectionnées * @param string|array $where Contraintes * @param string|array $groupby Regroupements * @param string|array $orderby Tris * @param string $limit Limites de résultats * @param string|array $having Contraintes posts sélections * @param string $serveur Nom de la connexion * @param bool $requeter Exécuter la requête, sinon la retourner * @return array|bool|resource|string * - string : Texte de la requête si on ne l'exécute pas * - ressource si requête exécutée, ressource pour fetch() * - false si la requête exécutée a ratée * - array : Tableau décrivant requête et temps d'exécution si var_profile actif pour tracer. */ function spip_sqlite_select( $select, $from, $where = '', $groupby = '', $orderby = '', $limit = '', $having = '', $serveur = '', $requeter = true ) { // version() n'est pas connu de sqlite $select = str_replace('version()', 'sqlite_version()', $select); // recomposer from $from = (!is_array($from) ? $from : _sqlite_calculer_select_as($from)); $query = _sqlite_calculer_expression('SELECT', $select, ', ') . _sqlite_calculer_expression('FROM', $from, ', ') . _sqlite_calculer_expression('WHERE', $where) . _sqlite_calculer_expression('GROUP BY', $groupby, ',') . _sqlite_calculer_expression('HAVING', $having) . ($orderby ? ("\nORDER BY " . _sqlite_calculer_order($orderby)) : '') . ($limit ? "\nLIMIT $limit" : ''); // dans un select, on doit renvoyer la requête en cas d'erreur $res = spip_sqlite_query($query, $serveur, $requeter); // texte de la requete demande ? if (!$requeter) { return $res; } // erreur survenue ? if ($res === false) { return spip_sqlite::traduire_requete($query, $serveur); } return $res; } /** * Sélectionne un fichier de base de données * * @param string $db * Nom de la base à utiliser * @param string $serveur * Nom du connecteur * @param bool $requeter * Inutilisé * * @return bool|string * - Nom de la base en cas de success. * - False en cas d'erreur. **/ function spip_sqlite_selectdb($db, $serveur = '', $requeter = true) { _sqlite_init(); // interdire la creation d'une nouvelle base, // sauf si on est dans l'installation if (!is_file($f = _DIR_DB . $db . '.sqlite') && (!defined('_ECRIRE_INSTALL') || !_ECRIRE_INSTALL) ) { spip_log("Il est interdit de creer la base $db", 'sqlite.' . _LOG_HS); return false; } // se connecter a la base indiquee // avec les identifiants connus $index = $serveur ? $serveur : 0; if ($link = spip_connect_db('', '', '', '', '@selectdb@' . $db, $serveur, '', '')) { if (($db == $link['db']) && $GLOBALS['connexions'][$index] = $link) { return $db; } } else { spip_log("Impossible de selectionner la base $db", 'sqlite.' . _LOG_HS); return false; } } /** * Définit un charset pour la connexion avec SQLite (aucune action ici) * * Cette fonction n'a aucune action actuellement. * * @param string $charset Charset à appliquer * @param string $serveur Nom de la connexion * @param bool $requeter inutilisé * @return void */ function spip_sqlite_set_charset($charset, $serveur = '', $requeter = true) { # spip_log("Gestion charset sql a ecrire : "."SET NAMES "._q($charset), 'sqlite.'._LOG_ERREUR); # return spip_sqlite_query("SET NAMES ". spip_sqlite_quote($charset), $serveur); //<-- Passe pas ! } /** * Retourne une ressource de la liste des tables de la base de données * * @param string $match * Filtre sur tables à récupérer * @param string $serveur * Connecteur de la base * @param bool $requeter * true pour éxecuter la requête * false pour retourner le texte de la requête. * @return ressource * Ressource à utiliser avec sql_fetch() **/ function spip_sqlite_showbase($match, $serveur = '', $requeter = true) { // type est le type d'entrée : table / index / view // on ne retourne que les tables (?) et non les vues... # ESCAPE non supporte par les versions sqlite <3 # return spip_sqlite_query("SELECT name FROM sqlite_master WHERE type='table' AND tbl_name LIKE "._q($match)." ESCAPE '\'", $serveur, $requeter); $match = preg_quote($match); $match = str_replace("\\\_", "[[TIRETBAS]]", $match); $match = str_replace("\\\%", "[[POURCENT]]", $match); $match = str_replace("_", ".", $match); $match = str_replace("%", ".*", $match); $match = str_replace("[[TIRETBAS]]", "_", $match); $match = str_replace("[[POURCENT]]", "%", $match); $match = "^$match$"; return spip_sqlite_query("SELECT name FROM sqlite_master WHERE type='table' AND tbl_name REGEXP " . _q($match), $serveur, $requeter); } define('_SQLITE_RE_SHOW_TABLE', '/^[^(),]*\(((?:[^()]*\((?:[^()]*\([^()]*\))?[^()]*\)[^()]*)*[^()]*)\)[^()]*$/'); /** * Obtient la description d'une table ou vue SQLite * * Récupère la définition d'une table ou d'une vue avec colonnes, indexes, etc. * au même format que la définition des tables SPIP, c'est à dire * un tableau avec les clés * * - `field` (tableau colonne => description SQL) et * - `key` (tableau type de clé => colonnes) * * @param string $nom_table Nom de la table SQL * @param string $serveur Nom de la connexion * @param bool $requeter Exécuter la requête, sinon la retourner * @return array|string * - chaîne vide si pas de description obtenue * - string Texte de la requête si demandé * - array description de la table sinon */ function spip_sqlite_showtable($nom_table, $serveur = '', $requeter = true) { $query = 'SELECT sql, type FROM' . ' (SELECT * FROM sqlite_master UNION ALL' . ' SELECT * FROM sqlite_temp_master)' . " WHERE tbl_name LIKE '$nom_table'" . " AND type!='meta' AND sql NOT NULL AND name NOT LIKE 'sqlite_%'" . ' ORDER BY substr(type,2,1), name'; $a = spip_sqlite_query($query, $serveur, $requeter); if (!$a) { return ""; } if (!$requeter) { return $a; } if (!($a = spip_sqlite_fetch($a, null, $serveur))) { return ""; } $vue = ($a['type'] == 'view'); // table | vue // c'est une table // il faut parser le create if (!$vue) { if (!preg_match(_SQLITE_RE_SHOW_TABLE, array_shift($a), $r)) { return ""; } else { $desc = $r[1]; // extraction d'une KEY éventuelle en prenant garde de ne pas // relever un champ dont le nom contient KEY (ex. ID_WHISKEY) if (preg_match("/^(.*?),([^,]*\sKEY[ (].*)$/s", $desc, $r)) { $namedkeys = $r[2]; $desc = $r[1]; } else { $namedkeys = ""; } $fields = array(); $keys = array(); // enlever les contenus des valeurs DEFAULT 'xxx' qui pourraient perturber // par exemple s'il contiennent une virgule. // /!\ cela peut aussi echapper le nom des champs si la table a eu des operations avec SQLite Manager ! list($desc, $echaps) = query_echappe_textes($desc); // separer toutes les descriptions de champs, separes par des virgules # /!\ explode peut exploser aussi DECIMAL(10,2) ! $k_precedent = null; foreach (explode(",", $desc) as $v) { preg_match("/^\s*([^\s]+)\s+(.*)/", $v, $r); // Les cles de champs peuvent etre entourees // de guillements doubles " , simples ', graves ` ou de crochets [ ], ou rien. // http://www.sqlite.org/lang_keywords.html $k = strtolower(query_reinjecte_textes($r[1], $echaps)); // champ, "champ", [champ]... if ($char = strpbrk($k[0], '\'"[`')) { $k = trim($k, $char); if ($char == '[') { $k = rtrim($k, ']'); } } $def = query_reinjecte_textes($r[2], $echaps); // valeur du champ # rustine pour DECIMAL(10,2) if (false !== strpos($k, ')')) { $fields[$k_precedent] .= ',' . $k . ' ' . $def; continue; } $fields[$k] = $def; $k_precedent = $k; // la primary key peut etre dans une des descriptions de champs // et non en fin de table, cas encore decouvert avec Sqlite Manager if (stripos($r[2], 'PRIMARY KEY') !== false) { $keys['PRIMARY KEY'] = $k; } } // key inclues dans la requete foreach (preg_split('/\)\s*(,|$)/', $namedkeys) as $v) { if (preg_match("/^\s*([^(]*)\(([^(]*(\(\d+\))?)$/", $v, $r)) { $k = str_replace("`", '', trim($r[1])); $t = trim(strtolower(str_replace("`", '', $r[2])), '"'); if ($k && !isset($keys[$k])) { $keys[$k] = $t; } else { $keys[] = $t; } } } // sinon ajouter les key index $query = 'SELECT name,sql FROM' . ' (SELECT * FROM sqlite_master UNION ALL' . ' SELECT * FROM sqlite_temp_master)' . " WHERE tbl_name LIKE '$nom_table'" . " AND type='index' AND name NOT LIKE 'sqlite_%'" . 'ORDER BY substr(type,2,1), name'; $a = spip_sqlite_query($query, $serveur, $requeter); while ($r = spip_sqlite_fetch($a, null, $serveur)) { $key = str_replace($nom_table . '_', '', $r['name']); // enlever le nom de la table ajoute a l'index $keytype = "KEY"; if (strpos($r['sql'], "UNIQUE INDEX") !== false) { $keytype = "UNIQUE KEY"; } $colonnes = preg_replace(',.*\((.*)\).*,', '$1', $r['sql']); $keys[$keytype . ' ' . $key] = $colonnes; } } } // c'est une vue, on liste les champs disponibles simplement else { if ($res = sql_fetsel('*', $nom_table, '', '', '', '1', '', $serveur)) { // limit 1 $fields = array(); foreach ($res as $c => $v) { $fields[$c] = ''; } $keys = array(); } else { return ""; } } return array('field' => $fields, 'key' => $keys); } /** * Met à jour des enregistrements d'une table SQL * * @param string $table * Nom de la table * @param array $champs * Couples (colonne => valeur) * @param string|array $where * Conditions a remplir (Where) * @param array $desc * Tableau de description des colonnes de la table SQL utilisée * (il sera calculé si nécessaire s'il n'est pas transmis). * @param string $serveur * Nom de la connexion * @param bool $requeter * Exécuter la requête, sinon la retourner * @return array|bool|string * - string : texte de la requête si demandé * - true si la requête a réussie, false sinon * - array Tableau décrivant la requête et son temps d'exécution si var_profile est actif */ function spip_sqlite_update($table, $champs, $where = '', $desc = '', $serveur = '', $requeter = true) { // recherche de champs 'timestamp' pour mise a jour auto de ceux-ci $champs = _sqlite_ajouter_champs_timestamp($table, $champs, $desc, $serveur); $set = array(); foreach ($champs as $champ => $val) { $set[] = $champ . "=$val"; } if (!empty($set)) { return spip_sqlite_query( _sqlite_calculer_expression('UPDATE', $table, ',') . _sqlite_calculer_expression('SET', $set, ',') . _sqlite_calculer_expression('WHERE', $where), $serveur, $requeter); } } /** * Met à jour des enregistrements d'une table SQL et protège chaque valeur * * Protège chaque valeur transmise avec sql_quote(), adapté au type * de champ attendu par la table SQL * * @param string $table * Nom de la table * @param array $champs * Couples (colonne => valeur) * @param string|array $where * Conditions a remplir (Where) * @param array $desc * Tableau de description des colonnes de la table SQL utilisée * (il sera calculé si nécessaire s'il n'est pas transmis). * @param string $serveur * Nom de la connexion * @param bool $requeter * Exécuter la requête, sinon la retourner * @return array|bool|string * - string : texte de la requête si demandé * - true si la requête a réussie, false sinon * - array Tableau décrivant la requête et son temps d'exécution si var_profile est actif */ function spip_sqlite_updateq($table, $champs, $where = '', $desc = array(), $serveur = '', $requeter = true) { if (!$champs) { return; } if (!$desc) { $desc = description_table($table, $serveur); } if (!$desc) { die("$table insertion sans description"); } $fields = $desc['field']; // recherche de champs 'timestamp' pour mise a jour auto de ceux-ci $champs = _sqlite_ajouter_champs_timestamp($table, $champs, $desc, $serveur); $set = array(); foreach ($champs as $champ => $val) { $set[] = $champ . '=' . _sqlite_calculer_cite($val, isset($fields[$champ]) ? $fields[$champ] : ''); } return spip_sqlite_query( _sqlite_calculer_expression('UPDATE', $table, ',') . _sqlite_calculer_expression('SET', $set, ',') . _sqlite_calculer_expression('WHERE', $where), $serveur, $requeter); } /* * * Ensuite les fonctions non abstraites * crees pour l'occasion de sqlite * */ /** * Initialise la première connexion à un serveur SQLite * * @return void */ function _sqlite_init() { if (!defined('_DIR_DB')) { define('_DIR_DB', _DIR_ETC . 'bases/'); } if (!defined('_SQLITE_CHMOD')) { define('_SQLITE_CHMOD', _SPIP_CHMOD); } if (!is_dir($d = _DIR_DB)) { include_spip('inc/flock'); sous_repertoire($d); } } /** * Teste la version sqlite du link en cours * * @param string $version * @param string $link * @param string $serveur * @param bool $requeter * @return bool|int */ function _sqlite_is_version($version = '', $link = '', $serveur = '', $requeter = true) { if ($link === '') { $link = _sqlite_link($serveur); } if (!$link) { return false; } if ($link instanceof PDO) { $v = 3; } else { $v = 2; } if (!$version) { return $v; } return ($version == $v); } /** * Retrouver un link d'une connexion SQLite * * @param string $serveur Nom du serveur * @return Object Information de connexion pour SQLite */ function _sqlite_link($serveur = '') { $link = &$GLOBALS['connexions'][$serveur ? $serveur : 0]['link']; return $link; } /* ordre alphabetique pour les autres */ /** * Renvoie les bons echappements (mais pas sur les fonctions comme NOW()) * * @param string|number $v Texte ou nombre à échapper * @param string $type Type de donnée attendue, description SQL de la colonne de destination * @return string|number Texte ou nombre échappé */ function _sqlite_calculer_cite($v, $type) { if ($type) { if (is_null($v) and stripos($type, "NOT NULL") === false ) { return 'NULL'; } // null php se traduit en NULL SQL if (sql_test_date($type) and preg_match('/^\w+\(/', $v)) { return $v; } if (sql_test_int($type)) { if (is_numeric($v)) { return $v; } elseif (ctype_xdigit(substr($v, 2)) and strncmp($v, '0x', 2) == 0) { return hexdec(substr($v, 2)); } else { return intval($v); } } } else { // si on ne connait pas le type on le deduit de $v autant que possible if (is_numeric($v)) { return strval($v); } } if (function_exists('sqlite_escape_string')) { return "'" . sqlite_escape_string($v) . "'"; } // trouver un link sqlite3 pour faire l'echappement foreach ($GLOBALS['connexions'] as $s) { if (_sqlite_is_version(3, $l = $s['link'])) { return $l->quote($v); } } // echapper les ' en '' spip_log("Pas de methode sqlite_escape_string ni ->quote pour echapper", "sqlite." . _LOG_INFO_IMPORTANTE); return ("'" . str_replace("'", "''", $v) . "'"); } /** * Calcule un expression pour une requête, en cumulant chaque élément * avec l'opérateur de liaison ($join) indiqué * * Renvoie grosso modo "$expression join($join, $v)" * * @param string $expression Mot clé de l'expression, tel que "WHERE" ou "ORDER BY" * @param array|string $v Données de l'expression * @param string $join Si les données sont un tableau, elles seront groupées par cette jointure * @return string Texte de l'expression, une partie donc, du texte la requête. */ function _sqlite_calculer_expression($expression, $v, $join = 'AND') { if (empty($v)) { return ''; } $exp = "\n$expression "; if (!is_array($v)) { return $exp . $v; } else { if (strtoupper($join) === 'AND') { return $exp . join("\n\t$join ", array_map('_sqlite_calculer_where', $v)); } else { return $exp . join($join, $v); } } } /** * Prépare une clause order by * * Regroupe en texte les éléments si un tableau est donné * * @note * Pas besoin de conversion pour 0+x comme il faudrait pour mysql. * * @param string|array $orderby Texte du orderby à préparer * @return string Texte du orderby préparé */ function _sqlite_calculer_order($orderby) { return (is_array($orderby)) ? join(", ", $orderby) : $orderby; } /** * Renvoie des `nom AS alias` * * @param array $args * @return string Sélection de colonnes pour une clause SELECT */ function _sqlite_calculer_select_as($args) { $res = ''; foreach ($args as $k => $v) { if (substr($k, -1) == '@') { // c'est une jointure qui se refere au from precedent // pas de virgule $res .= ' ' . $v; } else { if (!is_numeric($k)) { $p = strpos($v, " "); if ($p) { $v = substr($v, 0, $p) . " AS '$k'" . substr($v, $p); } else { $v .= " AS '$k'"; } } $res .= ', ' . $v; } } return substr($res, 2); } /** * Prépare une clause WHERE pour SQLite * * Retourne une chaîne avec les bonnes parenthèses pour la * contrainte indiquée, au format donnée par le compilateur * * @param array|string $v * Description des contraintes * - string : Texte du where * - sinon tableau : A et B peuvent être de type string ou array, * OP et C sont de type string : * - array(A) : A est le texte du where * - array(OP, A) : contrainte OP( A ) * - array(OP, A, B) : contrainte (A OP B) * - array(OP, A, B, C) : contrainte (A OP (B) : C) * @return string * Contrainte pour clause WHERE */ function _sqlite_calculer_where($v) { if (!is_array($v)) { return $v; } $op = array_shift($v); if (!($n = count($v))) { return $op; } else { $arg = _sqlite_calculer_where(array_shift($v)); if ($n == 1) { return "$op($arg)"; } else { $arg2 = _sqlite_calculer_where(array_shift($v)); if ($n == 2) { return "($arg $op $arg2)"; } else { return "($arg $op ($arg2) : $v[0])"; } } } } /** * Charger les modules SQLite * * Si possible et juste la version demandée, * ou, si aucune version, on renvoie les versions sqlite disponibles * sur ce serveur dans un tableau * * @param string $version * @return array|bool */ function _sqlite_charger_version($version = '') { $versions = array(); // version 2 if (!$version || $version == 2) { if (charger_php_extension('sqlite')) { $versions[] = 2; } } // version 3 if (!$version || $version == 3) { if (charger_php_extension('pdo') && charger_php_extension('pdo_sqlite')) { $versions[] = 3; } } if ($version) { return in_array($version, $versions); } return $versions; } /** * Gestion des requêtes ALTER non reconnues de SQLite * * Requêtes non reconnues : * * ALTER TABLE table DROP column * ALTER TABLE table CHANGE [COLUMN] columnA columnB definition * ALTER TABLE table MODIFY column definition * ALTER TABLE table ADD|DROP PRIMARY KEY * * `MODIFY` est transformé en `CHANGE columnA columnA` par spip_sqlite_alter() * * 1) Créer une table B avec le nouveau format souhaité * 2) Copier la table d'origine A vers B * 3) Supprimer la table A * 4) Renommer la table B en A * 5) Remettre les index (qui sont supprimés avec la table A) * * @param string|array $table * - string : Nom de la table table, * - array : couple (nom de la table => nom futur) * @param string|array $colonne * - string : nom de la colonne, * - array : couple (nom de la colonne => nom futur) * @param array $opt * options comme les tables SPIP, qui sera mergé à la table créee : * `array('field'=>array('nom'=>'syntaxe', ...), 'key'=>array('KEY nom'=>'colonne', ...))` * @param string $serveur * Nom de la connexion SQL en cours * @return bool * true si OK, false sinon. */ function _sqlite_modifier_table($table, $colonne, $opt = array(), $serveur = '') { if (is_array($table)) { reset($table); list($table_origine, $table_destination) = each($table); } else { $table_origine = $table_destination = $table; } // ne prend actuellement qu'un changement // mais pourra etre adapte pour changer plus qu'une colonne a la fois if (is_array($colonne)) { reset($colonne); list($colonne_origine, $colonne_destination) = each($colonne); } else { $colonne_origine = $colonne_destination = $colonne; } if (!isset($opt['field'])) { $opt['field'] = array(); } if (!isset($opt['key'])) { $opt['key'] = array(); } // si les noms de tables sont differents, pas besoin de table temporaire // on prendra directement le nom de la future table $meme_table = ($table_origine == $table_destination); $def_origine = sql_showtable($table_origine, false, $serveur); if (!$def_origine or !isset($def_origine['field'])) { spip_log("Alter table impossible sur $table_origine : table non trouvee", 'sqlite' . _LOG_ERREUR); return false; } $table_tmp = $table_origine . '_tmp'; // 1) creer une table temporaire avec les modifications // - DROP : suppression de la colonne // - CHANGE : modification de la colonne // (foreach pour conserver l'ordre des champs) // field $fields = array(); // pour le INSERT INTO plus loin // stocker la correspondance nouvelles->anciennes colonnes $fields_correspondances = array(); foreach ($def_origine['field'] as $c => $d) { if ($colonne_origine && ($c == $colonne_origine)) { // si pas DROP if ($colonne_destination) { $fields[$colonne_destination] = $opt['field'][$colonne_destination]; $fields_correspondances[$colonne_destination] = $c; } } else { $fields[$c] = $d; $fields_correspondances[$c] = $c; } } // cas de ADD sqlite2 (ajout du champ en fin de table): if (!$colonne_origine && $colonne_destination) { $fields[$colonne_destination] = $opt['field'][$colonne_destination]; } // key... $keys = array(); foreach ($def_origine['key'] as $c => $d) { $c = str_replace($colonne_origine, $colonne_destination, $c); $d = str_replace($colonne_origine, $colonne_destination, $d); // seulement si on ne supprime pas la colonne ! if ($d) { $keys[$c] = $d; } } // autres keys, on merge $keys = array_merge($keys, $opt['key']); $queries = array(); // copier dans destination (si differente de origine), sinon tmp $table_copie = ($meme_table) ? $table_tmp : $table_destination; $autoinc = (isset($keys['PRIMARY KEY']) and $keys['PRIMARY KEY'] and stripos($keys['PRIMARY KEY'], ',') === false and stripos($fields[$keys['PRIMARY KEY']], 'default') === false); if ($q = _sqlite_requete_create( $table_copie, $fields, $keys, $autoinc, $temporary = false, $ifnotexists = true, $serveur) ) { $queries[] = $q; } // 2) y copier les champs qui vont bien $champs_dest = join(', ', array_keys($fields_correspondances)); $champs_ori = join(', ', $fields_correspondances); $queries[] = "INSERT INTO $table_copie ($champs_dest) SELECT $champs_ori FROM $table_origine"; // 3) supprimer la table d'origine $queries[] = "DROP TABLE $table_origine"; // 4) renommer la table temporaire // avec le nom de la table destination // si necessaire if ($meme_table) { if (_sqlite_is_version(3, '', $serveur)) { $queries[] = "ALTER TABLE $table_copie RENAME TO $table_destination"; } else { $queries[] = _sqlite_requete_create( $table_destination, $fields, $keys, $autoinc, $temporary = false, $ifnotexists = false, // la table existe puisqu'on est dans une transaction $serveur); $queries[] = "INSERT INTO $table_destination SELECT * FROM $table_copie"; $queries[] = "DROP TABLE $table_copie"; } } // 5) remettre les index ! foreach ($keys as $k => $v) { if ($k == 'PRIMARY KEY') { } else { // enlever KEY $k = substr($k, 4); $queries[] = "CREATE INDEX $table_destination" . "_$k ON $table_destination ($v)"; } } if (count($queries)) { spip_sqlite::demarrer_transaction($serveur); // il faut les faire une par une car $query = join('; ', $queries).";"; ne fonctionne pas foreach ($queries as $q) { if (!spip_sqlite::executer_requete($q, $serveur)) { spip_log(_LOG_GRAVITE_ERREUR, "SQLite : ALTER TABLE table :" . " Erreur a l'execution de la requete : $q", 'sqlite'); spip_sqlite::annuler_transaction($serveur); return false; } } spip_sqlite::finir_transaction($serveur); } return true; } /** * Nom des fonctions * * @return array */ function _sqlite_ref_fonctions() { $fonctions = array( 'alter' => 'spip_sqlite_alter', 'count' => 'spip_sqlite_count', 'countsel' => 'spip_sqlite_countsel', 'create' => 'spip_sqlite_create', 'create_base' => 'spip_sqlite_create_base', 'create_view' => 'spip_sqlite_create_view', 'date_proche' => 'spip_sqlite_date_proche', 'delete' => 'spip_sqlite_delete', 'drop_table' => 'spip_sqlite_drop_table', 'drop_view' => 'spip_sqlite_drop_view', 'errno' => 'spip_sqlite_errno', 'error' => 'spip_sqlite_error', 'explain' => 'spip_sqlite_explain', 'fetch' => 'spip_sqlite_fetch', 'seek' => 'spip_sqlite_seek', 'free' => 'spip_sqlite_free', 'hex' => 'spip_sqlite_hex', 'in' => 'spip_sqlite_in', 'insert' => 'spip_sqlite_insert', 'insertq' => 'spip_sqlite_insertq', 'insertq_multi' => 'spip_sqlite_insertq_multi', 'listdbs' => 'spip_sqlite_listdbs', 'multi' => 'spip_sqlite_multi', 'optimize' => 'spip_sqlite_optimize', 'query' => 'spip_sqlite_query', 'quote' => 'spip_sqlite_quote', 'repair' => 'spip_sqlite_repair', 'replace' => 'spip_sqlite_replace', 'replace_multi' => 'spip_sqlite_replace_multi', 'select' => 'spip_sqlite_select', 'selectdb' => 'spip_sqlite_selectdb', 'set_charset' => 'spip_sqlite_set_charset', 'get_charset' => 'spip_sqlite_get_charset', 'showbase' => 'spip_sqlite_showbase', 'showtable' => 'spip_sqlite_showtable', 'update' => 'spip_sqlite_update', 'updateq' => 'spip_sqlite_updateq', 'preferer_transaction' => 'spip_sqlite_preferer_transaction', 'demarrer_transaction' => 'spip_sqlite_demarrer_transaction', 'terminer_transaction' => 'spip_sqlite_terminer_transaction', ); // association de chaque nom http d'un charset aux couples sqlite // SQLite supporte utf-8 et utf-16 uniquement. $charsets = array( 'utf-8' => array('charset' => 'utf8', 'collation' => 'utf8_general_ci'), //'utf-16be'=>array('charset'=>'utf16be','collation'=>'UTF-16BE'),// aucune idee de quoi il faut remplir dans es champs la //'utf-16le'=>array('charset'=>'utf16le','collation'=>'UTF-16LE') ); $fonctions['charsets'] = $charsets; return $fonctions; } /** * $query est une requete ou une liste de champs * * @param $query * @param bool $autoinc * @return mixed */ function _sqlite_remplacements_definitions_table($query, $autoinc = false) { // quelques remplacements $num = "(\s*\([0-9]*\))?"; $enum = "(\s*\([^\)]*\))?"; $remplace = array( '/enum' . $enum . '/is' => 'VARCHAR(255)', '/COLLATE \w+_bin/is' => 'COLLATE BINARY', '/COLLATE \w+_ci/is' => 'COLLATE NOCASE', '/auto_increment/is' => '', '/(timestamp .* )ON .*$/is' => '\\1', '/character set \w+/is' => '', '/((big|small|medium|tiny)?int(eger)?)' . $num . '\s*unsigned/is' => '\\1 UNSIGNED', '/(text\s+not\s+null(\s+collate\s+\w+)?)\s*$/is' => "\\1 DEFAULT ''", '/((char|varchar)' . $num . '\s+not\s+null(\s+collate\s+\w+)?)\s*$/is' => "\\1 DEFAULT ''", '/(datetime\s+not\s+null)\s*$/is' => "\\1 DEFAULT '0000-00-00 00:00:00'", '/(date\s+not\s+null)\s*$/is' => "\\1 DEFAULT '0000-00-00'", ); // pour l'autoincrement, il faut des INTEGER NOT NULL PRIMARY KEY $remplace_autocinc = array( '/(big|small|medium|tiny)?int(eger)?' . $num . '/is' => 'INTEGER' ); // pour les int non autoincrement, il faut un DEFAULT $remplace_nonautocinc = array( '/((big|small|medium|tiny)?int(eger)?' . $num . '\s+not\s+null)\s*$/is' => "\\1 DEFAULT 0", ); if (is_string($query)) { $query = preg_replace(array_keys($remplace), $remplace, $query); if ($autoinc or preg_match(',AUTO_INCREMENT,is', $query)) { $query = preg_replace(array_keys($remplace_autocinc), $remplace_autocinc, $query); } else { $query = preg_replace(array_keys($remplace_nonautocinc), $remplace_nonautocinc, $query); $query = _sqlite_collate_ci($query); } } elseif (is_array($query)) { foreach ($query as $k => $q) { $ai = ($autoinc ? $k == $autoinc : preg_match(',AUTO_INCREMENT,is', $q)); $query[$k] = preg_replace(array_keys($remplace), $remplace, $query[$k]); if ($ai) { $query[$k] = preg_replace(array_keys($remplace_autocinc), $remplace_autocinc, $query[$k]); } else { $query[$k] = preg_replace(array_keys($remplace_nonautocinc), $remplace_nonautocinc, $query[$k]); $query[$k] = _sqlite_collate_ci($query[$k]); } } } return $query; } /** * Definir la collation d'un champ en fonction de si une collation est deja explicite * et du par defaut que l'on veut NOCASE * * @param string $champ * @return string */ function _sqlite_collate_ci($champ) { if (stripos($champ, "COLLATE") !== false) { return $champ; } if (stripos($champ, "BINARY") !== false) { return str_ireplace("BINARY", "COLLATE BINARY", $champ); } if (preg_match(",^(char|varchar|(long|small|medium|tiny)?text),i", $champ)) { return $champ . " COLLATE NOCASE"; } return $champ; } /** * Creer la requete pour la creation d'une table * retourne la requete pour utilisation par sql_create() et sql_alter() * * @param $nom * @param $champs * @param $cles * @param bool $autoinc * @param bool $temporary * @param bool $_ifnotexists * @param string $serveur * @param bool $requeter * @return bool|string */ function _sqlite_requete_create( $nom, $champs, $cles, $autoinc = false, $temporary = false, $_ifnotexists = true, $serveur = '', $requeter = true ) { $query = $keys = $s = $p = ''; // certains plugins declarent les tables (permet leur inclusion dans le dump) // sans les renseigner (laisse le compilo recuperer la description) if (!is_array($champs) || !is_array($cles)) { return; } // sqlite ne gere pas KEY tout court dans une requete CREATE TABLE // il faut passer par des create index // Il gere par contre primary key ! // Soit la PK est definie dans les cles, soit dans un champs // soit faussement dans les 2 (et dans ce cas, il faut l’enlever à un des 2 endroits !) $pk = "PRIMARY KEY"; // le champ de cle primaire $c = !empty($cles[$pk]) ? $cles[$pk] : ''; foreach ($champs as $k => $v) { if (false !== stripos($v, $pk)) { $c = $k; // on n'en a plus besoin dans field, vu que defini dans key $champs[$k] = preg_replace("/$pk/is", '', $champs[$k]); break; } } if ($c) { $keys = "\n\t\t$pk ($c)"; } // Pas de DEFAULT 0 sur les cles primaires en auto-increment if (isset($champs[$c]) and stripos($champs[$c], "default 0") !== false ) { $champs[$c] = trim(str_ireplace("default 0", "", $champs[$c])); } $champs = _sqlite_remplacements_definitions_table($champs, $autoinc ? $c : false); foreach ($champs as $k => $v) { $query .= "$s\n\t\t$k $v"; $s = ","; } $ifnotexists = ""; if ($_ifnotexists) { $version = spip_sqlite_fetch(spip_sqlite_query("select sqlite_version() AS sqlite_version", $serveur), '', $serveur); if (!function_exists('spip_version_compare')) { include_spip('plugins/installer'); } if ($version and spip_version_compare($version['sqlite_version'], '3.3.0', '>=')) { $ifnotexists = ' IF NOT EXISTS'; } else { /* simuler le IF EXISTS - version 2 et sqlite < 3.3a */ $a = spip_sqlite_showtable($nom, $serveur); if (isset($a['key']['KEY ' . $nom])) { return true; } } } $temporary = $temporary ? ' TEMPORARY' : ''; $q = "CREATE$temporary TABLE$ifnotexists $nom ($query" . ($keys ? ",$keys" : '') . ")\n"; return $q; } /** * Retrouver les champs 'timestamp' * pour les ajouter aux 'insert' ou 'replace' * afin de simuler le fonctionnement de mysql * * stocke le resultat pour ne pas faire * de requetes showtable intempestives * * @param $table * @param $couples * @param string $desc * @param string $serveur * @return */ function _sqlite_ajouter_champs_timestamp($table, $couples, $desc = '', $serveur = '') { static $tables = array(); if (!isset($tables[$table])) { if (!$desc) { $trouver_table = charger_fonction('trouver_table', 'base'); $desc = $trouver_table($table, $serveur); // si pas de description, on ne fait rien, ou on die() ? if (!$desc) { return $couples; } } // recherche des champs avec simplement 'TIMESTAMP' // cependant, il faudra peut etre etendre // avec la gestion de DEFAULT et ON UPDATE // mais ceux-ci ne sont pas utilises dans le core $tables[$table] = array(); foreach ($desc['field'] as $k => $v) { if (strpos(strtolower(ltrim($v)), 'timestamp') === 0) { $tables[$table][$k] = "datetime('now')"; } } } // ajout des champs type 'timestamp' absents return array_merge($tables[$table], $couples); } /** * Renvoyer la liste des versions sqlite disponibles * sur le serveur * * @return array|bool */ function spip_versions_sqlite() { return _sqlite_charger_version(); } /** * Gère l'envoi et la réception de requêtes à SQLite, qui peuvent être * encadrées de transactions. **/ class spip_sqlite { /** @var sqlite_requeteur[] Liste des instances de requêteurs créés */ public static $requeteurs = array(); /** @var bool[] Pour chaque connexion, flag pour savoir si une transaction est en cours */ public static $transaction_en_cours = array(); /** * Retourne une unique instance du requêteur * * Retourne une instance unique du requêteur pour une connexion SQLite * donnée * * @param string $serveur * Nom du connecteur * @return sqlite_requeteur * Instance unique du requêteur **/ public static function requeteur($serveur) { if (!isset(spip_sqlite::$requeteurs[$serveur])) { spip_sqlite::$requeteurs[$serveur] = new sqlite_requeteur($serveur); } return spip_sqlite::$requeteurs[$serveur]; } /** * Prépare le texte d'une requête avant son exécution * * Adapte la requête au format plus ou moins MySQL par un format * compris de SQLite. * * Change les préfixes de tables SPIP par ceux véritables * * @param string $query Requête à préparer * @param string $serveur Nom de la connexion * @return string Requête préparée */ public static function traduire_requete($query, $serveur) { $requeteur = spip_sqlite::requeteur($serveur); $traducteur = new sqlite_traducteur($query, $requeteur->prefixe, $requeteur->sqlite_version); return $traducteur->traduire_requete(); } /** * Démarre une transaction * * @param string $serveur Nom de la connexion **/ public static function demarrer_transaction($serveur) { spip_sqlite::executer_requete("BEGIN TRANSACTION", $serveur); spip_sqlite::$transaction_en_cours[$serveur] = true; } /** * Exécute la requête donnée * * @param string $query Requête * @param string $serveur Nom de la connexion * @param null|bool $tracer Demander des statistiques (temps) ? **/ public static function executer_requete($query, $serveur, $tracer = null) { $requeteur = spip_sqlite::requeteur($serveur); return $requeteur->executer_requete($query, $tracer); } /** * Obtient l'identifiant de la dernière ligne insérée ou modifiée * * @param string $serveur Nom de la connexion * return int Identifiant **/ public static function last_insert_id($serveur) { $requeteur = spip_sqlite::requeteur($serveur); return $requeteur->last_insert_id($serveur); } /** * Annule une transaction * * @param string $serveur Nom de la connexion **/ public static function annuler_transaction($serveur) { spip_sqlite::executer_requete("ROLLBACK", $serveur); spip_sqlite::$transaction_en_cours[$serveur] = false; } /** * Termine une transaction * * @param string $serveur Nom de la connexion **/ public static function finir_transaction($serveur) { // si pas de transaction en cours, ne rien faire et le dire if (!isset(spip_sqlite::$transaction_en_cours[$serveur]) or spip_sqlite::$transaction_en_cours[$serveur] == false ) { return false; } // sinon fermer la transaction et retourner true spip_sqlite::executer_requete("COMMIT", $serveur); spip_sqlite::$transaction_en_cours[$serveur] = false; return true; } } /* * Classe pour partager les lancements de requête * * Instanciée une fois par `$serveur` : * * - peut corriger la syntaxe des requêtes pour la conformité à SQLite * - peut tracer les requêtes */ class sqlite_requeteur { /** @var string Texte de la requête */ public $query = ''; // la requete /** @var string Nom de la connexion */ public $serveur = ''; /** @var Ressource Identifiant de la connexion SQLite */ public $link = ''; /** @var string Prefixe des tables SPIP */ public $prefixe = ''; /** @var string Nom de la base de donnée */ public $db = ''; /** @var bool Doit-on tracer les requetes (var_profile) ? */ public $tracer = false; // doit-on tracer les requetes (var_profile) /** @var string Version de SQLite (2 ou 3) */ public $sqlite_version = ''; /** * Constructeur * * @param string $serveur * @return bool */ public function __construct($serveur = '') { _sqlite_init(); $this->serveur = strtolower($serveur); if (!($this->link = _sqlite_link($this->serveur)) && (!defined('_ECRIRE_INSTALL') || !_ECRIRE_INSTALL)) { spip_log("Aucune connexion sqlite (link)", 'sqlite.' . _LOG_ERREUR); return false; } $this->sqlite_version = _sqlite_is_version('', $this->link); $this->prefixe = $GLOBALS['connexions'][$this->serveur ? $this->serveur : 0]['prefixe']; $this->db = $GLOBALS['connexions'][$this->serveur ? $this->serveur : 0]['db']; // tracage des requetes ? $this->tracer = (isset($_GET['var_profile']) && $_GET['var_profile']); } /** * Lancer la requête transmise et faire le tracage si demandé * * @param string $query * Requête à exécuter * @param bool|null $tracer * true pour tracer la requête * @return bool|SQLiteResult */ public function executer_requete($query, $tracer = null) { if (is_null($tracer)) { $tracer = $this->tracer; } $err = ""; $t = 0; if ($tracer) { include_spip('public/tracer'); $t = trace_query_start(); } # spip_log("requete: $this->serveur >> $query",'sqlite.'._LOG_DEBUG); // boum ? pourquoi ? if ($this->link) { // memoriser la derniere erreur PHP vue $e = (function_exists('error_get_last') ? error_get_last() : ""); // sauver la derniere requete $GLOBALS['connexions'][$this->serveur ? $this->serveur : 0]['last'] = $query; $GLOBALS['connexions'][$this->serveur ? $this->serveur : 0]['total_requetes']++; if ($this->sqlite_version == 3) { $r = $this->link->query($query); // sauvegarde de la requete (elle y est deja dans $r->queryString) # $r->spipQueryString = $query; // comptage : oblige de compter le nombre d'entrees retournees // par une requete SELECT // aucune autre solution ne donne le nombre attendu :( ! // particulierement s'il y a des LIMIT dans la requete. if (strtoupper(substr(ltrim($query), 0, 6)) == 'SELECT') { if ($r) { // noter le link et la query pour faire le comptage *si* on en a besoin $r->spipSqliteRowCount = array($this->link, $query); } elseif ($r instanceof PDOStatement) { $r->spipSqliteRowCount = 0; } } } else { $r = sqlite_query($this->link, $query); } // loger les warnings/erreurs eventuels de sqlite remontant dans PHP if ($err = (function_exists('error_get_last') ? error_get_last() : "") and $err != $e) { $err = strip_tags($err['message']) . " in " . $err['file'] . " line " . $err['line']; spip_log("$err - " . $query, 'sqlite.' . _LOG_ERREUR); } else { $err = ""; } } else { $r = false; } if (spip_sqlite_errno($this->serveur)) { $err .= spip_sqlite_error($query, $this->serveur); } return $t ? trace_query_end($query, $t, $r, $err, $this->serveur) : $r; } /** * Obtient l'identifiant de la dernière ligne insérée ou modifiée * * @return int **/ public function last_insert_id() { if ($this->sqlite_version == 3) { return $this->link->lastInsertId(); } else { return sqlite_last_insert_rowid($this->link); } } } /** * Cette classe est presente essentiellement pour un preg_replace_callback * avec des parametres dans la fonction appelee que l'on souhaite incrementer * (fonction pour proteger les textes) */ class sqlite_traducteur { /** @var string $query Texte de la requête */ public $query = ''; /** @var string $prefixe Préfixe des tables */ public $prefixe = ''; /** @var string $sqlite_version Version de sqlite (2 ou 3) */ public $sqlite_version = ''; /** Pour les corrections à effectuer sur les requêtes : array(code=>'texte') trouvé * * @var array */ public $textes = array(); /** * Constructeur * * @param string $query Requête à préparer * @param string $prefixe Prefixe des tables à utiliser * @param string $sqlite_version Version SQLite (2 ou 3) */ public function __construct($query, $prefixe, $sqlite_version) { $this->query = $query; $this->prefixe = $prefixe; $this->sqlite_version = $sqlite_version; } /** * Transformer la requete pour SQLite * * Enlève les textes, transforme la requête pour quelle soit * bien interprétée par SQLite, puis remet les textes * la fonction affecte `$this->query` */ public function traduire_requete() { // // 1) Protection des textes en les remplacant par des codes // // enlever les 'textes' et initialiser avec list($this->query, $textes) = query_echappe_textes($this->query); // // 2) Corrections de la requete // // Correction Create Database // Create Database -> requete ignoree if (strpos($this->query, 'CREATE DATABASE') === 0) { spip_log("Sqlite : requete non executee -> $this->query", 'sqlite.' . _LOG_AVERTISSEMENT); $this->query = "SELECT 1"; } // Correction Insert Ignore // INSERT IGNORE -> insert (tout court et pas 'insert or replace') if (strpos($this->query, 'INSERT IGNORE') === 0) { spip_log("Sqlite : requete transformee -> $this->query", 'sqlite.' . _LOG_DEBUG); $this->query = 'INSERT ' . substr($this->query, '13'); } // Correction des dates avec INTERVAL // utiliser sql_date_proche() de preference if (strpos($this->query, 'INTERVAL') !== false) { $this->query = preg_replace_callback("/DATE_(ADD|SUB)(.*)INTERVAL\s+(\d+)\s+([a-zA-Z]+)\)/U", array(&$this, '_remplacerDateParTime'), $this->query); } if (strpos($this->query, 'LEFT(') !== false) { $this->query = str_replace('LEFT(', '_LEFT(', $this->query); } if (strpos($this->query, 'TIMESTAMPDIFF(') !== false) { $this->query = preg_replace('/TIMESTAMPDIFF\(\s*([^,]*)\s*,/Uims', "TIMESTAMPDIFF('\\1',", $this->query); } // Correction Using // USING (non reconnu en sqlite2) // problematique car la jointure ne se fait pas du coup. if (($this->sqlite_version == 2) && (strpos($this->query, "USING") !== false)) { spip_log("'USING (champ)' n'est pas reconnu en SQLite 2. Utilisez 'ON table1.champ = table2.champ'", 'sqlite.' . _LOG_ERREUR); $this->query = preg_replace('/USING\s*\([^\)]*\)/', '', $this->query); } // Correction Field // remplace FIELD(table,i,j,k...) par CASE WHEN table=i THEN n ... ELSE 0 END if (strpos($this->query, 'FIELD') !== false) { $this->query = preg_replace_callback('/FIELD\s*\(([^\)]*)\)/', array(&$this, '_remplacerFieldParCase'), $this->query); } // Correction des noms de tables FROM // mettre les bons noms de table dans from, update, insert, replace... if (preg_match('/\s(SET|VALUES|WHERE|DATABASE)\s/iS', $this->query, $regs)) { $suite = strstr($this->query, $regs[0]); $this->query = substr($this->query, 0, -strlen($suite)); } else { $suite = ''; } $pref = ($this->prefixe) ? $this->prefixe . "_" : ""; $this->query = preg_replace('/([,\s])spip_/S', '\1' . $pref, $this->query) . $suite; // Correction zero AS x // pg n'aime pas 0+x AS alias, sqlite, dans le meme style, // n'apprecie pas du tout SELECT 0 as x ... ORDER BY x // il dit que x ne doit pas être un integer dans le order by ! // on remplace du coup x par vide() dans ce cas uniquement // // rien que pour public/vertebrer.php ? if ((strpos($this->query, "0 AS") !== false)) { // on ne remplace que dans ORDER BY ou GROUP BY if (preg_match('/\s(ORDER|GROUP) BY\s/i', $this->query, $regs)) { $suite = strstr($this->query, $regs[0]); $this->query = substr($this->query, 0, -strlen($suite)); // on cherche les noms des x dans 0 AS x // on remplace dans $suite le nom par vide() preg_match_all('/\b0 AS\s*([^\s,]+)/', $this->query, $matches, PREG_PATTERN_ORDER); foreach ($matches[1] as $m) { $suite = str_replace($m, 'VIDE()', $suite); } $this->query .= $suite; } } // Correction possible des divisions entieres // Le standard SQL (lequel? ou?) semble indiquer que // a/b=c doit donner c entier si a et b sont entiers 4/3=1. // C'est ce que retournent effectivement SQL Server et SQLite // Ce n'est pas ce qu'applique MySQL qui retourne un reel : 4/3=1.333... // // On peut forcer la conversion en multipliant par 1.0 avant la division // /!\ SQLite 3.5.9 Debian/Ubuntu est victime d'un bug en plus ! // cf. https://bugs.launchpad.net/ubuntu/+source/sqlite3/+bug/254228 // http://www.sqlite.org/cvstrac/tktview?tn=3202 // (4*1.0/3) n'est pas rendu dans ce cas ! # $this->query = str_replace('/','* 1.00 / ',$this->query); // Correction critere REGEXP, non reconnu en sqlite2 if (($this->sqlite_version == 2) && (strpos($this->query, 'REGEXP') !== false)) { $this->query = preg_replace('/([^\s\(]*)(\s*)REGEXP(\s*)([^\s\)]*)/', 'REGEXP($4, $1)', $this->query); } // // 3) Remise en place des textes d'origine // // Correction Antiquotes et echappements // ` => rien if (strpos($this->query, '`') !== false) { $this->query = str_replace('`', '', $this->query); } $this->query = query_reinjecte_textes($this->query, $textes); return $this->query; } /** * Callback pour remplacer `DATE_` / `INTERVAL` * par `DATE ... strtotime` * * @param array $matches Captures * @return string Texte de date compris par SQLite */ public function _remplacerDateParTime($matches) { $op = strtoupper($matches[1] == 'ADD') ? '+' : '-'; return "datetime$matches[2] '$op$matches[3] $matches[4]')"; } /** * Callback pour remplacer `FIELD(table,i,j,k...)` * par `CASE WHEN table=i THEN n ... ELSE 0 END` * * @param array $matches Captures * @return string Texte de liste ordonnée compris par SQLite */ public function _remplacerFieldParCase($matches) { $fields = substr($matches[0], 6, -1); // ne recuperer que l'interieur X de field(X) $t = explode(',', $fields); $index = array_shift($t); $res = ''; $n = 0; foreach ($t as $v) { $n++; $res .= "\nWHEN $index=$v THEN $n"; } return "CASE $res ELSE 0 END "; } }
| ver. 1.4 |
Github
|
.
| PHP 7.0.33 | Генерация страницы: 0 |
proxy
|
phpinfo
|
Настройка