You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

2290 lines
63 KiB

<?php
/* *************************************************************************\
* SPIP, Système de publication pour l'internet *
18 years ago
* *
* Copyright © avec tendresse depuis 2001 *
* Arnaud Martin, Antoine Pitrou, Philippe Rivière, Emmanuel Saint-James *
18 years ago
* *
* Ce programme est un logiciel libre distribué sous licence GNU/GPL. *
* Pour plus de détails voir le fichier COPYING.txt ou l'aide en ligne. *
18 years ago
\***************************************************************************/
/**
* Definition de l'API SQL
*
* Ce fichier definit la couche d'abstraction entre SPIP et ses serveurs SQL.
* Cette version 1 est un ensemble de fonctions ecrites rapidement
* pour generaliser le code strictement MySQL de SPIP <= 1.9.2
* Des retouches sont a prevoir apres l'experience des premiers portages.
* Les symboles sql_* (constantes et nom de fonctions) sont reserves
* a cette interface, sans quoi le gestionnaire de version dysfonctionnera.
*
* @package SPIP\Core\SQL\API
* @version 1
*/
if (!defined('_ECRIRE_INC_VERSION')) {
return;
}
/** Version de l'API SQL */
Introduction d'un gestionnaire de version d'interface SQL. Afin de prévenir les désagréments d'éventuels changements de spécification, l'interface de SPIP aux serveurs SQL inrègre d'emblée un gestionnaire de versions, permettant à une même connexcion SQL d'être exploitée {simultanément} par plusieurs versions des fonctions d'abstraction. Et également fourni un script rendant automatiquement compatibles des extensions fondées sur d'anciennes versions de l'interface. Principe. Toutes les fonctions de l'interface sont définies dans le fichier (((ecrire/base/abstract_sql))), se nomment {{{sql_}}}{X} et sont les seules à se nommées ainsi. Elles se connectent toutes en appelant une fonction dont le premier argument est le numéro de version de l'interface. Le jour où une nouvelle version de (((ecrire/base/abstract_sql))) apparaitra nécessaire, la version courante sera renommée {{{abstract_sql_}}}{{N}}, et le Sed suivant lui sera appliqué ({{{N}}} désigne le numéro de version): {{{ s/\(sql_[A-Za-z_0-9 ]*\)/\1_N/ }}} En appliquant également ce script aux extensions de SPIP fondées sur cette version, on leur permettra d'en appeler ses fonctions, qui seront chargées sans collision de noms, le Sed ayant préfixé le noms des anciennes avec leur numéro de version. Il faudra juste rajouter une instruction {{{include}}} portant sur le fichier {{{abstract_sql_}}}{{N}}. Ce service repose sur la réservation des noms commençant par {{{sql_}}} aux seules fonctions d'interface, grâce aux renommages effectués en [9916] [9918] et [9919]. Les extensions de SPIP doivent respecter cette contrainte s'ils veulent en bénéficier. Le présent dépot consiste en une reconception de la sructure de données décrivant une connexion, afin d'associer plusieurs jeux de fonction à une même connexion.
15 years ago
define('sql_ABSTRACT_VERSION', 1);
13 years ago
include_spip('base/connect_sql');
Introduction d'un gestionnaire de version d'interface SQL. Afin de prévenir les désagréments d'éventuels changements de spécification, l'interface de SPIP aux serveurs SQL inrègre d'emblée un gestionnaire de versions, permettant à une même connexcion SQL d'être exploitée {simultanément} par plusieurs versions des fonctions d'abstraction. Et également fourni un script rendant automatiquement compatibles des extensions fondées sur d'anciennes versions de l'interface. Principe. Toutes les fonctions de l'interface sont définies dans le fichier (((ecrire/base/abstract_sql))), se nomment {{{sql_}}}{X} et sont les seules à se nommées ainsi. Elles se connectent toutes en appelant une fonction dont le premier argument est le numéro de version de l'interface. Le jour où une nouvelle version de (((ecrire/base/abstract_sql))) apparaitra nécessaire, la version courante sera renommée {{{abstract_sql_}}}{{N}}, et le Sed suivant lui sera appliqué ({{{N}}} désigne le numéro de version): {{{ s/\(sql_[A-Za-z_0-9 ]*\)/\1_N/ }}} En appliquant également ce script aux extensions de SPIP fondées sur cette version, on leur permettra d'en appeler ses fonctions, qui seront chargées sans collision de noms, le Sed ayant préfixé le noms des anciennes avec leur numéro de version. Il faudra juste rajouter une instruction {{{include}}} portant sur le fichier {{{abstract_sql_}}}{{N}}. Ce service repose sur la réservation des noms commençant par {{{sql_}}} aux seules fonctions d'interface, grâce aux renommages effectués en [9916] [9918] et [9919]. Les extensions de SPIP doivent respecter cette contrainte s'ils veulent en bénéficier. Le présent dépot consiste en une reconception de la sructure de données décrivant une connexion, afin d'associer plusieurs jeux de fonction à une même connexion.
15 years ago
7 years ago
/**
* Retourne la pile de fonctions utilisée lors de la précence d'une erreur SQL
*
* @note
* Ignore les fonctions `include_once`, `include_spip`, `find_in_path`
* @param bool $compil_info
* - false : Retourne un texte présentant les fonctions utilisées
* - true : retourne un tableau indiquant un contexte de compilation à l'origine de la requête,
* utile pour présenter une erreur au débuggueur via `erreur_squelette()`
* @return array|string
* contexte de l'erreur
**/
function sql_error_backtrace($compil_info = false) {
$trace = debug_backtrace();
$caller = array_shift($trace);
while (count($trace) and (empty($trace[0]['file']) or $trace[0]['file'] === $caller['file'] or $trace[0]['file'] === __FILE__)) {
array_shift($trace);
}
if ($compil_info) {
$contexte_compil = array(
$trace[0]['file'],// sourcefile
'', //nom
(isset($trace[1]) ? $trace[1]['function'] . "(){\n" : '')
. $trace[0]['function'] . "();"
. (isset($trace[1]) ? "\n}" : ''), //id_boucle
$trace[0]['line'], // ligne
$GLOBALS['spip_lang'], // lang
);
return $contexte_compil;
}
$message = count($trace) ? $trace[0]['file'] . " L" . $trace[0]['line'] : "";
$f = array();
while (count($trace) and $t = array_shift($trace)) {
if (in_array($t['function'], array('include_once', 'include_spip', 'find_in_path'))) {
break;
7 years ago
}
$f[] = $t['function'];
}
7 years ago
if (count($f)) {
$message .= " [" . implode("(),", $f) . "()]";
7 years ago
}
return $message;
}
/**
* Charge le serveur de base de donnees
*
* Fonction principale. Elle charge l'interface au serveur de base de donnees
* via la fonction spip_connect_version qui etablira la connexion au besoin.
* Elle retourne la fonction produisant la requete SQL demandee
* Erreur fatale si la fonctionnalite est absente sauf si le 3e arg <> false
*
* @internal Cette fonction de base est appelee par les autres fonctions sql_*
* @param string $ins_sql
* Instruction de l'API SQL demandee (insertq, update, select...)
* @param string $serveur
* Nom du connecteur ('' pour celui par defaut a l'installation de SPIP)
* @param bool $continue
* true pour ne pas generer d'erreur si le serveur SQL ne dispose pas de la fonction demandee
* @return string|array
* Nom de la fonction a appeler pour l'instruction demandee pour le type de serveur SQL correspondant au fichier de connexion.
* Si l'instruction demandee n'existe pas, retourne la liste de toutes les instructions du serveur SQL avec $continue a true.
*
**/
function sql_serveur($ins_sql = '', $serveur = '', $continue = false) {
static $sql_serveur = array();
if (!isset($sql_serveur[$serveur][$ins_sql])) {
$f = spip_connect_sql(sql_ABSTRACT_VERSION, $ins_sql, $serveur, $continue);
if (!is_string($f) or !$f) {
return $f;
}
$sql_serveur[$serveur][$ins_sql] = $f;
}
return $sql_serveur[$serveur][$ins_sql];
}
/**
* Demande si un charset est disponible
*
* Demande si un charset (tel que utf-8) est disponible
* sur le gestionnaire de base de données de la connexion utilisée
*
* @api
* @see sql_set_charset() pour utiliser un charset
*
* @param string $charset
* Le charset souhaité
* @param string $serveur
* Le nom du connecteur
* @param bool $option
* Inutilise
* @return string|bool
* Retourne le nom du charset si effectivement trouvé, sinon false.
**/
function sql_get_charset($charset, $serveur = '', $option = true) {
// le nom http du charset differe parfois du nom SQL utf-8 ==> utf8 etc.
$desc = sql_serveur('', $serveur, true, true);
Introduction d'un gestionnaire de version d'interface SQL. Afin de prévenir les désagréments d'éventuels changements de spécification, l'interface de SPIP aux serveurs SQL inrègre d'emblée un gestionnaire de versions, permettant à une même connexcion SQL d'être exploitée {simultanément} par plusieurs versions des fonctions d'abstraction. Et également fourni un script rendant automatiquement compatibles des extensions fondées sur d'anciennes versions de l'interface. Principe. Toutes les fonctions de l'interface sont définies dans le fichier (((ecrire/base/abstract_sql))), se nomment {{{sql_}}}{X} et sont les seules à se nommées ainsi. Elles se connectent toutes en appelant une fonction dont le premier argument est le numéro de version de l'interface. Le jour où une nouvelle version de (((ecrire/base/abstract_sql))) apparaitra nécessaire, la version courante sera renommée {{{abstract_sql_}}}{{N}}, et le Sed suivant lui sera appliqué ({{{N}}} désigne le numéro de version): {{{ s/\(sql_[A-Za-z_0-9 ]*\)/\1_N/ }}} En appliquant également ce script aux extensions de SPIP fondées sur cette version, on leur permettra d'en appeler ses fonctions, qui seront chargées sans collision de noms, le Sed ayant préfixé le noms des anciennes avec leur numéro de version. Il faudra juste rajouter une instruction {{{include}}} portant sur le fichier {{{abstract_sql_}}}{{N}}. Ce service repose sur la réservation des noms commençant par {{{sql_}}} aux seules fonctions d'interface, grâce aux renommages effectués en [9916] [9918] et [9919]. Les extensions de SPIP doivent respecter cette contrainte s'ils veulent en bénéficier. Le présent dépot consiste en une reconception de la sructure de données décrivant une connexion, afin d'associer plusieurs jeux de fonction à une même connexion.
15 years ago
$desc = $desc[sql_ABSTRACT_VERSION];
$c = $desc['charsets'][$charset];
if ($c) {
if (function_exists($f = @$desc['get_charset'])) {
if ($f($c, $serveur, $option !== false)) {
return $c;
}
}
}
spip_log("SPIP ne connait pas les Charsets disponibles sur le serveur $serveur. Le serveur choisira seul.",
_LOG_AVERTISSEMENT);
return false;
}
/**
* Regler le codage de connexion
*
* Affecte un charset (tel que utf-8) sur la connexion utilisee
* avec le gestionnaire de base de donnees
*
* @api
* @see sql_get_charset() pour tester l'utilisation d'un charset
*
* @param string $charset
* Le charset souhaite
* @param string $serveur
* Le nom du connecteur
* @param bool|string $option
* Peut avoir 2 valeurs :
* - true pour executer la requete.
* - continue pour ne pas echouer en cas de serveur sql indisponible.
*
* @return bool
* Retourne true si elle reussie.
**/
function sql_set_charset($charset, $serveur = '', $option = true) {
$f = sql_serveur('set_charset', $serveur, $option === 'continue' or $option === false);
if (!is_string($f) or !$f) {
return false;
}
return $f($charset, $serveur, $option !== false);
}
/**
10 years ago
* Effectue une requête de selection
*
* Fonction de selection (SELECT), retournant la ressource interrogeable par sql_fetch.
*
* @api
* @see sql_fetch() Pour boucler sur les resultats de cette fonction
*
* @param array|string $select
* Liste des champs a recuperer (Select)
* @param array|string $from
* Tables a consulter (From)
* @param array|string $where
* Conditions a remplir (Where)
* @param array|string $groupby
* Critere de regroupement (Group by)
* @param array|string $orderby
* Tableau de classement (Order By)
* @param string $limit
* Critere de limite (Limit)
* @param array $having
* Tableau des des post-conditions a remplir (Having)
* @param string $serveur
* Le serveur sollicite (pour retrouver la connexion)
* @param bool|string $option
* Peut avoir 3 valeurs :
*
* - false -> ne pas l'exécuter mais la retourner,
* - continue -> ne pas echouer en cas de serveur sql indisponible,
* - true|array -> executer la requête.
* Le cas array est, pour une requete produite par le compilateur,
* un tableau donnnant le contexte afin d'indiquer le lieu de l'erreur au besoin
*
*
* @return mixed
* Ressource SQL
*
* - Ressource SQL pour sql_fetch, si la requete est correcte
* - false en cas d'erreur
* - Chaine contenant la requete avec $option=false
*
* Retourne false en cas d'erreur, apres l'avoir denoncee.
* Les portages doivent retourner la requete elle-meme en cas d'erreur,
* afin de disposer du texte brut.
*
**/
function sql_select(
$select = array(),
$from = array(),
$where = array(),
$groupby = array(),
$orderby = array(),
$limit = '',
$having = array(),
$serveur = '',
$option = true
) {
$f = sql_serveur('select', $serveur, $option === 'continue' or $option === false);
if (!is_string($f) or !$f) {
return false;
}
$debug = (defined('_VAR_MODE') and _VAR_MODE == 'debug');
if (($option !== false) and !$debug) {
$res = $f($select, $from, $where, $groupby, $orderby, $limit, $having, $serveur,
is_array($option) ? true : $option);
} else {
$query = $f($select, $from, $where, $groupby, $orderby, $limit, $having, $serveur, false);
if (!$option) {
return $query;
}
// le debug, c'est pour ce qui a ete produit par le compilateur
if (isset($GLOBALS['debug']['aucasou'])) {
list($table, $id, ) = $GLOBALS['debug']['aucasou'];
$nom = $GLOBALS['debug_objets']['courant'] . $id;
$GLOBALS['debug_objets']['requete'][$nom] = $query;
}
$res = $f($select, $from, $where, $groupby, $orderby, $limit, $having, $serveur, true);
}
// en cas d'erreur
if (!is_string($res)) {
return $res;
}
// denoncer l'erreur SQL dans sa version brute
spip_sql_erreur($serveur);
// idem dans sa version squelette (prefixe des tables non substitue)
$contexte_compil = sql_error_backtrace(true);
erreur_squelette(array(sql_errno($serveur), sql_error($serveur), $res), $contexte_compil);
return false;
}
/**
* Recupere la syntaxe de la requete select sans l'executer
*
* Passe simplement $option a false au lieu de true
* sans obliger a renseigner tous les arguments de sql_select.
* Les autres parametres sont identiques.
*
* @api
* @uses sql_select()
*
* @param array|string $select
* Liste des champs a recuperer (Select)
* @param array|string $from
* Tables a consulter (From)
* @param array|string $where
* Conditions a remplir (Where)
* @param array|string $groupby
* Critere de regroupement (Group by)
* @param array|string $orderby
* Tableau de classement (Order By)
* @param string $limit
* Critere de limite (Limit)
* @param array $having
* Tableau des des post-conditions a remplir (Having)
* @param string $serveur
* Le serveur sollicite (pour retrouver la connexion)
*
* @return mixed
* Chaine contenant la requete
* ou false en cas d'erreur
*
**/
function sql_get_select(
$select = array(),
$from = array(),
$where = array(),
$groupby = array(),
$orderby = array(),
$limit = '',
$having = array(),
$serveur = ''
) {
return sql_select($select, $from, $where, $groupby, $orderby, $limit, $having, $serveur, false);
}
/**
* Retourne le nombre de lignes d'une sélection
*
* Ramène seulement et tout de suite le nombre de lignes
* Pas de colonne ni de tri à donner donc.
*
* @api
* @see sql_count()
* @example
* ```
* if (sql_countsel('spip_mots_liens', array(
* "objet=".sql_quote('article'),
* "id_article=".sql_quote($id_article)) > 0) {
* // ...
* }
* ```
*
* @param array|string $from
* Tables a consulter (From)
* @param array|string $where
* Conditions a remplir (Where)
* @param array|string $groupby
* Critere de regroupement (Group by)
* @param array $having
* Tableau des des post-conditions a remplir (Having)
* @param string $serveur
* Le serveur sollicite (pour retrouver la connexion)
* @param bool|string $option
* Peut avoir 3 valeurs :
*
* - false -> ne pas l'executer mais la retourner,
* - continue -> ne pas echouer en cas de serveur sql indisponible,
* - true -> executer la requete.
*
* @return int|bool
* - Nombre de lignes de resultat
* - ou false en cas d'erreur
*
**/
function sql_countsel(
$from = array(),
$where = array(),
$groupby = array(),
$having = array(),
$serveur = '',
$option = true
) {
$f = sql_serveur('countsel', $serveur, $option === 'continue' or $option === false);
if (!is_string($f) or !$f) {
return false;
}
$r = $f($from, $where, $groupby, $having, $serveur, $option !== false);
if ($r === false) {
spip_sql_erreur($serveur);
}
return $r;
}
/**
* Modifie la structure de la base de données
*
* Effectue une opération ALTER.
*
* @example
* ```
* sql_alter('DROP COLUMN supprimer');
* ```
*
* @api
* @param string $q
* La requête à exécuter (sans la préceder de 'ALTER ')
* @param string $serveur
* Le serveur sollicite (pour retrouver la connexion)
* @param bool|string $option
* Peut avoir 2 valeurs :
*
* - true : exécuter la requete
* - 'continue' : ne pas échouer en cas de serveur sql indisponible
* @return mixed
* 2 possibilités :
*
* - Incertain en cas d'exécution correcte de la requête
* - false en cas de serveur indiponible ou d'erreur
*
* Ce retour n'est pas pertinent pour savoir si l'opération est correctement réalisée.
**/
function sql_alter($q, $serveur = '', $option = true) {
$f = sql_serveur('alter', $serveur, $option === 'continue' or $option === false);
if (!is_string($f) or !$f) {
return false;
}
$r = $f($q, $serveur, $option !== false);
if ($r === false) {
spip_sql_erreur($serveur);
}
return $r;
}
/**
* Retourne un enregistrement d'une selection
*
* Retourne un resultat d'une ressource obtenue avec sql_select()
*
* @api
* @param mixed $res
* Ressource retournee par sql_select()
* @param string $serveur
* Le nom du connecteur
* @param bool|string $option
* Peut avoir 2 valeurs :
* - true -> executer la requete
* - continue -> ne pas echouer en cas de serveur sql indisponible
*
* @return array
* Tableau de cles (colonnes SQL ou alias) / valeurs (valeurs dans la colonne de la table ou calculee)
* presentant une ligne de resultat d'une selection
*/
function sql_fetch($res, $serveur = '', $option = true) {
$f = sql_serveur('fetch', $serveur, $option === 'continue' or $option === false);
if (!is_string($f) or !$f) {
return false;
}
return $f($res, null, $serveur, $option !== false);
}
/**
* Retourne tous les enregistrements d'une selection
*
* Retourne tous les resultats d'une ressource obtenue avec sql_select()
* dans un tableau
*
* @api
* @param mixed $res
* Ressource retournee par sql_select()
* @param string $serveur
* Le nom du connecteur
* @param bool|string $option
* Peut avoir 2 valeurs :
* - true -> executer la requete
* - continue -> ne pas echouer en cas de serveur sql indisponible
*
* @return array
* Tableau contenant les enregistrements.
* Chaque entree du tableau est un autre tableau
* de cles (colonnes SQL ou alias) / valeurs (valeurs dans la colonne de la table ou calculee)
* presentant une ligne de resultat d'une selection
*/
function sql_fetch_all($res, $serveur = '', $option = true) {
$rows = array();
if (!$res) {
return $rows;
}
$f = sql_serveur('fetch', $serveur, $option === 'continue' or $option === false);
if (!is_string($f) or !$f) {
return array();
}
while ($r = $f($res, null, $serveur, $option !== false)) {
$rows[] = $r;
}
sql_free($res, $serveur);
return $rows;
}
/**
10 years ago
* Déplace le pointeur d'une ressource de sélection
*
10 years ago
* Deplace le pointeur sur un numéro de ligne précisé
* sur une ressource issue de sql_select, afin que
10 years ago
* le prochain sql_fetch récupère cette ligne.
*
* @api
* @see sql_skip() Pour sauter des enregistrements
*
* @param mixed $res
* Ressource issue de sql_select
* @param int $row_number
* Numero de ligne sur laquelle placer le pointeur
* @param string $serveur
* Le nom du connecteur
* @param bool|string $option
* Peut avoir 2 valeurs :
* - true -> executer la requete
* - continue -> ne pas echouer en cas de serveur sql indisponible
*
* @return bool
* Operation effectuée (true), sinon false.
**/
function sql_seek($res, $row_number, $serveur = '', $option = true) {
$f = sql_serveur('seek', $serveur, $option === 'continue' or $option === false);
if (!is_string($f) or !$f) {
return false;
}
$r = $f($res, $row_number, $serveur, $option !== false);
if ($r === false) {
spip_sql_erreur($serveur);
}
return $r;
}
/**
* Liste des bases de donnees accessibles
*
* Retourne un tableau du nom de toutes les bases de donnees
* accessibles avec les permissions de l'utilisateur SQL
* de cette connexion.
* Attention on n'a pas toujours les droits !
*
* @api
* @param string $serveur
* Nom du connecteur
* @param bool|string $option
* Peut avoir 2 valeurs :
* - true -> executer la requete
* - continue -> ne pas echouer en cas de serveur sql indisponible
*
* @return array|bool
* Tableau contenant chaque nom de base de donnees.
* False en cas d'erreur.
**/
function sql_listdbs($serveur = '', $option = true) {
$f = sql_serveur('listdbs', $serveur, $option === 'continue' or $option === false);
if (!is_string($f) or !$f) {
return false;
}
$r = $f($serveur);
if ($r === false) {
spip_sql_erreur($serveur);
}
return $r;
}
/**
* Demande d'utiliser d'une base de donnees
*
* @api
* @param string $nom
* Nom de la base a utiliser
* @param string $serveur
* Nom du connecteur
* @param bool|string $option
* Peut avoir 2 valeurs :
*
* - true -> executer la requete
* - continue -> ne pas echouer en cas de serveur sql indisponible