spip_nursit/ecrire/req/sqlite_generique.php

3199 lines
92 KiB
PHP
Raw Normal View History

2023-06-01 15:30:12 +00: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 dune 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' 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 met à jour une entrée dune 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 met à jour des entrées dune 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)
// s'il y a une parenthèse fermante dans la clé
// ou dans la définition sans qu'il n'y ait une ouverture avant
if (false !== strpos($k, ')') or preg_match('/^[^\(]*\)/', $def)) {
$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)) {
list($table_origine, $table_destination) = reset($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)) {
list($colonne_origine, $colonne_destination) = reset($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;
}
/**
* Adapte les déclarations des champs pour SQLite
*
* @param string|array $query Déclaration dun champ ou liste de déclarations de champs
* @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' => '',
'/current_timestamp\(\)/is' => 'CURRENT_TIMESTAMP', // Fix export depuis mariaDB #4374
'/(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 lenlever à 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();
$now = _sqlite_func_now();
foreach ($desc['field'] as $k => $v) {
if (strpos(strtolower(ltrim($v)), 'timestamp') === 0) {
$tables[$table][$k] = _sqlite_calculer_cite($now, $v);
}
}
}
// 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 ";
}
}