File "Traducteur.php"
Full path: /home/argothem/www/organecyberpresse/ecrire/src/Sql/Sqlite/Traducteur.php
File size: 6.97 KB
MIME-type: text/x-php
Charset: utf-8
<?php
namespace Spip\Sql\Sqlite;
/**
* 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 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 = [];
/**
* 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',
[&$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*\(([^\)]*)\)/',
[&$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
//
// apparait dans public/vertebrer.php et dans le plugin menu aussi qui genere aussi ce genre de requete via un {par num #GET{tri_num}}
// mais est-ce encore un soucis pour sqlite en 2021 ? (ie commenter le preg_replace marche très bien en sqlite 3.28)
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) {
if (strpos($suite, $m) !== false) {
$suite = preg_replace(",\b$m\b,", '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 ";
}
}