[mySQL] problème d'accents dans query

23 réponses
Avatar
Denis Beauregard
Bonjour,

Sur un site que j'ai développé il y a quelques années, je pouvais
faire une recherche telle que si je recherche une lettre accentuée,
mysql trouve la lettre accentuée ou non. Ainsi, la recherche suivante

http://www.sgcf.com/zacharie/reponse.php?Auteurs=b%E9auregard&Titre=&Cote=&Sujet=

où je demande béauregard, va trouver beauregard.

Je suis en train de développer un nouveau site à partir de zéro et
j'ai donc créé une nouvelle base de données et écrit du code
entièrement nouveau. Mais la procédure pour préparer le query est
à peu près la même.

ancienne version : $mot = $_GET puis str_replace pour remplacer * par
%
et l'espace par % puis addslashes et $query = "select etc.

nouvelle version : $mot = valeur("mot") où valeur est cette fonction:

function Valeur ($cible, $def) {
$rep = $def;
if (isset($_POST[$cible])) { $rep =$_POST[$cible]; };
if (isset($_GET[$cible])) { $rep =$_GET[$cible]; };
$rep = str_replace("*","%",$rep);
$rep = str_replace("\"","",$rep);
$rep = str_replace(";","",$rep);
$rep = str_replace("?","_",$rep);
return (addslashes($rep));
};

donc, je prends l'argument dans $_GET ou $_POST, je remplace les
* et ? par des équivalents SQL, et j'enlève les \ et ; pour me
protéger contre des infections de code. Les modifications sont
équivalentes.

Dans les deux cas, le SELECT utilise LIKE pour la comparaison.
Mais dans mon nouveau code, hébert ne trouve pas hebert !

Le champ dans la base SQL est pourtant codé comme
latin1_general_ci dans les deux cas, l'un est text et l'autre
un varchar(36). Dans les deux cas, pas d'énoncé dans l'entête
pour choisir un jeu de caractères.

Quel est le problème de mon nouveau code ?


Denis

10 réponses

1 2 3
Avatar
Mickaël Wolff
Denis Beauregard a écrit :

nouvelle version : $mot = valeur("mot") où valeur est cette fonction:

function Valeur ($cible, $def) {
$rep = $def;
if (isset($_POST[$cible])) { $rep =$_POST[$cible]; };
if (isset($_GET[$cible])) { $rep =$_GET[$cible]; };



// si tu ne fais pas la différence entre POST et GET, autant
// utiliser REQUEST
$rep = empty($_REQUEST[$cible]) ? $def : $_REQUEST[$cible] ;
// Je ne sais quel extension tu utilises, mais en gros, il FAUT utiliser
// la fonction de protection des requêtes.
//<http://kuza55.blogspot.com/2007/06/mysql-injection-encoding-attacks.html>

$rep = mysql_real_escape_string($rep) ;
$rep = str_replace(array('*', '?') ,array("%", '_'), $rep);

return (addslashes($rep));


// Les parenthèses derrière un return c'est pour dire que tu ne
comprends pas ce que tu écris ? Et addslashes, c'est le mal © (cf lien
plus haut)

};


Il n'y a pas de point-virgule à la fin de la définition d'une fonction.

donc, je prends l'argument dans $_GET ou $_POST, je remplace les
* et ? par des équivalents SQL, et j'enlève les et ; pour me
protéger contre des infections de code. Les modifications sont
équivalentes.


La protection est caduque, comme dis ci-dessus.

Dans les deux cas, le SELECT utilise LIKE pour la comparaison.
Mais dans mon nouveau code, hébert ne trouve pas hebert !


Tout d'abord, like peut mettre par terre ton serveur. C'est
extrêmement consommateur de temps CPU et de mémoire. Like ne peut,
généralement, utiliser les index lorsqu'ils existent.

Le champ dans la base SQL est pourtant codé comme
latin1_general_ci dans les deux cas, l'un est text et l'autre
un varchar(36). Dans les deux cas, pas d'énoncé dans l'entête
pour choisir un jeu de caractères.


Quel est le create de la table ? quel encoding est utilisé à la
connexion, et quel sont les encoding/charset des sources PHP ?

--
Mickaël Wolff aka Lupus Michaelis
http://lupusmic.org
Avatar
Denis Beauregard
Le Fri, 04 Sep 2009 12:18:02 +0200, Mickaël Wolff
écrivait dans fr.comp.applications.sgbd:

Denis Beauregard a écrit :

nouvelle version : $mot = valeur("mot") où valeur est cette fonction:

function Valeur ($cible, $def) {
$rep = $def;
if (isset($_POST[$cible])) { $rep =$_POST[$cible]; };
if (isset($_GET[$cible])) { $rep =$_GET[$cible]; };



// si tu ne fais pas la différence entre POST et GET, autant
// utiliser REQUEST
$rep = empty($_REQUEST[$cible]) ? $def : $_REQUEST[$cible] ;



d'accord

// Je ne sais quel extension tu utilises, mais en gros, il FAUT utiliser
// la fonction de protection des requêtes.
//<http://kuza55.blogspot.com/2007/06/mysql-injection-encoding-attacks.html>

$rep = mysql_real_escape_string($rep) ;
$rep = str_replace(array('*', '?') ,array("%", '_'), $rep);



Hum, easyphp par défaut ne fait pas de addslashes alors que mon
serveur est configuré pour en faire un. J'ai ajouté un stripslashes
(l'heureux doit trouver l heureux, lheureux et l'heureux, donc
' devient % mais avant d'ajouter le )

return (addslashes($rep));


// Les parenthèses derrière un return c'est pour dire que tu ne
comprends pas ce que tu écris ? Et addslashes, c'est le mal © (cf lien
plus haut)



Non, c'est que je me simplifie la vie en faisant tout de la même
façon. J'utilise plusieurs langages informatiques. De plus, je
programme en C et C++ avec un éditeur que j'ai écrit en 1985-1988
et qui a un analyseur syntaxique de 4k (écrit en ASM), ce qui me
dit tout de suite quand j'ai une erreur de syntaxe. Comme je n'ai
que 4k pour le faire, je ne peux pas faire un cas à part pour
return et je fais donc toujours return (), ce qui est acceptable
même si moins optimal.

};


Il n'y a pas de point-virgule à la fin de la définition d'une fonction.



Même raison.

donc, je prends l'argument dans $_GET ou $_POST, je remplace les
* et ? par des équivalents SQL, et j'enlève les et ; pour me
protéger contre des infections de code. Les modifications sont
équivalentes.


La protection est caduque, comme dis ci-dessus.



Merci pour le lien, je vois maintenant comment on injecte ce genre
de code. Ceci dit, dans l'ancien projet (où je ne suis plus
impliqué), il faut au moins connaître un nom d'usager, sinon on
est limité à zéro recherche par jour. Et dans le nouveau, le
problème serait lié à un vol de session car c'est une autre partie
du code qui valide les usagers. Mon bout de code ne fait que
gérer la recherche dans la base de données.

Dans les deux cas, le SELECT utilise LIKE pour la comparaison.
Mais dans mon nouveau code, hébert ne trouve pas hebert !


Tout d'abord, like peut mettre par terre ton serveur. C'est
extrêmement consommateur de temps CPU et de mémoire. Like ne peut,
généralement, utiliser les index lorsqu'ils existent.



LIKE est la seule façon que je connaisse pour faire une recherche
avec des jetons. Je veux que mon usager puisse saisir ARSEN pour
rechercher parmi les ARSENEAU, ARSENEAUX, ARSENAULT, etc. (j'ajoute
le % à la fin, à la demande du client).

Le champ dans la base SQL est pourtant codé comme
latin1_general_ci dans les deux cas, l'un est text et l'autre
un varchar(36). Dans les deux cas, pas d'énoncé dans l'entête
pour choisir un jeu de caractères.


Quel est le create de la table ? quel encoding est utilisé à la
connexion, et quel sont les encoding/charset des sources PHP ?



Comme je disais, aucun énoncé de charset ou encoding dans les sources
PHP, même pas dans l'en-tête. Mais cela fonctionne dans le 1er projet
et pas dans le 2e.

Un extrait du code (après un export de phpmysql)

CREATE TABLE `rechindex` (
`defunt` varchar(36) collate latin1_general_ci NOT NULL default '',
`conjoint` text collate latin1_general_ci NOT NULL,
// autres champs similaires
`id` bigint(20) NOT NULL default '0',
PRIMARY KEY (`id`),
KEY `defunt` (`defunt`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 COLLATE=latin1_general_ci
COMMENT='Nom du projet';


La recherche se fait sur le champ defunt et la table est ordonnée par
ce champ avant son importation sur le serveur. Avec une recherche
sans tri, cela prend autour de 20 secondes pour le pire cas, et avec
tri, cela descend à 2 secondes (sauf l'heureux qui devient l%heureux
et qui est beaucoup plus lent).


Denis
Avatar
Denis Beauregard
Le Wed, 02 Sep 2009 22:59:20 -0400, Denis Beauregard
écrivait dans
fr.comp.applications.sgbd:

Sur un site que j'ai développé il y a quelques années, je pouvais
faire une recherche telle que si je recherche une lettre accentuée,
mysql trouve la lettre accentuée ou non. Ainsi, la recherche suivante

http://www.sgcf.com/zacharie/reponse.php?Auteurs=b%E9auregard&Titre=&Cote=&Sujet >
où je demande béauregard, va trouver beauregard.



Problème résolu.

L'ancienne base, qui fonctionne, était en latin1_swedish, alors que la
nouvelle, qui ne fonctionne pas avec les accents, était en
latin1_general.

Il semble donc qu'en français, il faille choisir le suédois...


Denis
Avatar
Mickaël Wolff
Denis Beauregard a écrit :

Problème résolu.


Tant mieux ! :)

L'ancienne base, qui fonctionne, était en latin1_swedish, alors que la
nouvelle, qui ne fonctionne pas avec les accents, était en
latin1_general.


Je me doutais d'un truc comme ça, c'est pour ça que je t'avais
demandé le create. En fait, l'ancienne base ne « fonctionnait » pas, ce
qui te fournissait le comportement bogué que tu considère comme étant le
comportement normal.

Il semble donc qu'en français, il faille choisir le suédois...


En fait, c'est pas vraiment ça. C'est le problème de l'ordre
lexicographique qui veut qu'en français, les lettes é è et ê n'ont pas
la même valeur que le e. Alors qu'il semblerait qu'en suédois, si. Si
toutefois les glyphes y existent.

--
Mickaël Wolff aka Lupus Michaelis
http://lupusmic.org
Avatar
Denis Beauregard
Le Wed, 09 Sep 2009 03:28:37 +0200, Mickaël Wolff
écrivait dans fr.comp.applications.sgbd:

Denis Beauregard a écrit :

Problème résolu.


Tant mieux ! :)

L'ancienne base, qui fonctionne, était en latin1_swedish, alors que la
nouvelle, qui ne fonctionne pas avec les accents, était en
latin1_general.


Je me doutais d'un truc comme ça, c'est pour ça que je t'avais
demandé le create. En fait, l'ancienne base ne « fonctionnait » pas, ce
qui te fournissait le comportement bogué que tu considère comme étant le
comportement normal.

Il semble donc qu'en français, il faille choisir le suédois...


En fait, c'est pas vraiment ça. C'est le problème de l'ordre
lexicographique qui veut qu'en français, les lettes é è et ê n'ont pas
la même valeur que le e. Alors qu'il semblerait qu'en suédois, si. Si
toutefois les glyphes y existent.



Le choix est parmi les suivants :

latin1_bin Europe de l'ouest (multilingue), Binaire
latin1_danish_ci danois, insensible à la casse
latin1_general_ci Europe de l'ouest (multilingue), insensible à
la casse
latin1_general_cs Europe de l'ouest (multilingue), sensible à la
casse
latin1_german1_ci allemand (dictionnaire), insensible à la casse
latin1_german2_ci allemand (annuaire téléphonique), insensible à
la casse
latin1_spanish_ci espagnol, insensible à la casse
latin1_swedish_ci suédois, insensible à la casse

Les données viennent d'un fichier Access je pense (on me fournit la
base de données), donc c'est un jeu Windows 1252. Mais comme c'est
pour le Web, je préfère un jeu iso, donc latin1. Pour mySQL, leur
latin1 est en réalité du 1252, donc cela ne change pas grand chose !

http://dev.mysql.com/doc/refman/5.0/fr/charset-we-sets.html montre
la liste des jeux de caractères, mais pas de leur contenu. Ce que
je trouve étrange, c'est que latin1 est en soi un jeu de caractères,
donc que latin1_swedish_ci et latin1_general_ci devraient avoir les
mêmes codes pour les mêmes lettres. Ce qui semble bien le cas !

Mais si je compare les pages suivantes :
http://www.collation-charts.org/mysql60/mysql604.latin1_general_ci.html
http://www.collation-charts.org/mysql60/mysql604.latin1_swedish_ci.html
la réponse finisse par apparaître.

general distingue les accents, donc à ne trouvera pas â, alors que
swedish ne les distingue pas, donc à ou â trouveront a ou ä

C'est dommage que cette distinction très importante ne soit pas plus
mise en lumière. J'ai lu le manuel de mysql et les sites bidons qui
sont favorisés par l'algorythme farfelu de Google (préférence donnée
au nombre de pages sur un site et aux blogues et non aux sites qui
devraient être la référence). Je n'ai pas vu d'emphase sur la
différence, peut-être parce que tout le monde utilise la valeur par
défaut...


Denis
Avatar
Pascal G.
Bonjour


Dans les deux cas, le SELECT utilise LIKE pour la comparaison.
Mais dans mon nouveau code, hébert ne trouve pas hebert !


Tout d'abord, like peut mettre par terre ton serveur. C'est
extrêmement consommateur de temps CPU et de mémoire. Like ne peut,
généralement, utiliser les index lorsqu'ils existent.



LIKE est la seule façon que je connaisse pour faire une recherche
avec des jetons. Je veux que mon usager puisse saisir ARSEN pour
rechercher parmi les ARSENEAU, ARSENEAUX, ARSENAULT, etc. (j'ajoute
le % à la fin, à la demande du client).




Tu peux utiliser les expressions régulières de MySQL pour t'affranchir de
LIKE (et effectuer des recherches correspondant au début d'une chaine).

http://dev.mysql.com/doc/refman/5.1/en/regexp.html

@+
Pascal
Avatar
Pascal G.
Dans les deux cas, le SELECT utilise LIKE pour la comparaison.
Mais dans mon nouveau code, hébert ne trouve pas hebert !



Tout d'abord, like peut mettre par terre ton serveur. C'est
extrêmement consommateur de temps CPU et de mémoire. Like ne peut,
généralement, utiliser les index lorsqu'ils existent.



LIKE est la seule façon que je connaisse pour faire une recherche
avec des jetons. Je veux que mon usager puisse saisir ARSEN pour
rechercher parmi les ARSENEAU, ARSENEAUX, ARSENAULT, etc. (j'ajoute
le % à la fin, à la demande du client).




Tu peux utiliser les expressions régulières de MySQL pour t'affranchir de
LIKE (et effectuer des recherches correspondant au début d'une chaine).

http://dev.mysql.com/doc/refman/5.1/en/regexp.html




Donc par exemple :

SELECT * FROM table WHERE nom REGEXP "^ARSEN"

En revanche, je n'ai aucune idée du gain (ou pas) avec LIKE.


@+
Pascal
Avatar
Denis Beauregard
Le Wed, 9 Sep 2009 18:52:52 +0200, "Pascal G."
écrivait dans fr.comp.applications.sgbd:


Dans les deux cas, le SELECT utilise LIKE pour la comparaison.
Mais dans mon nouveau code, hébert ne trouve pas hebert !









Tout d'abord, like peut mettre par terre ton serveur. C'est
extrêmement consommateur de temps CPU et de mémoire. Like ne peut,
généralement, utiliser les index lorsqu'ils existent.



LIKE est la seule façon que je connaisse pour faire une recherche
avec des jetons. Je veux que mon usager puisse saisir ARSEN pour
rechercher parmi les ARSENEAU, ARSENEAUX, ARSENAULT, etc. (j'ajoute
le % à la fin, à la demande du client).




Tu peux utiliser les expressions régulières de MySQL pour t'affranchir de
LIKE (et effectuer des recherches correspondant au début d'une chaine).

http://dev.mysql.com/doc/refman/5.1/en/regexp.html




Donc par exemple :

SELECT * FROM table WHERE nom REGEXP "^ARSEN"

En revanche, je n'ai aucune idée du gain (ou pas) avec LIKE.



Pour l'évaluer (ma base de test a plus de 2 millions
d'enregistrements), il faudrait que je trouve la fonction PHP qui me
donne le temps en millisecondes. Pour le moment, je n'ai que celle
avec les secondes, time().


Denis
Avatar
Pascal G.
Bonsoir

Dans les deux cas, le SELECT utilise LIKE pour la comparaison.
Mais dans mon nouveau code, hébert ne trouve pas hebert !









Tout d'abord, like peut mettre par terre ton serveur. C'est
extrêmement consommateur de temps CPU et de mémoire. Like ne peut,
généralement, utiliser les index lorsqu'ils existent.



LIKE est la seule façon que je connaisse pour faire une recherche
avec des jetons. Je veux que mon usager puisse saisir ARSEN pour
rechercher parmi les ARSENEAU, ARSENEAUX, ARSENAULT, etc. (j'ajoute
le % à la fin, à la demande du client).




Tu peux utiliser les expressions régulières de MySQL pour t'affranchir
de
LIKE (et effectuer des recherches correspondant au début d'une chaine).



En revanche, je n'ai aucune idée du gain (ou pas) avec LIKE.



Pour l'évaluer (ma base de test a plus de 2 millions
d'enregistrements), il faudrait que je trouve la fonction PHP qui me
donne le temps en millisecondes. Pour le moment, je n'ai que celle
avec les secondes, time().



microtime() te la donne en ms.

Je viens d'effectuer l'exemple donné dans l'article en mettant
usleep(2000000); et j'ai eu en résultat : "Did nothing in 1.999960899353
seconds", ce qui semble quand même assez précis. (ps : je suis avec un
portable, OS vista que j'utile pour un peu de dév et démo, et une
installation WAMP !).

http://www.php.net/manual/en/function.microtime.php

Ma réflexion sur l'idée du gain ou pas de REGEXP faisait suite au propos de
Mickäel W. Sans entrer dans l'étude d'un bench en php (qui peut être serait
hors charte), je serai assez surpris qu'il n'y ait pas ici un habitué qui
sache déjà ce qu'il est préférable d'utiliser (LIKE ou REGEXP).

Et en faisant une petite recherche Internet, la synthèse des 4 ou 5 pages
que j'ai parcourues semblent indiquer que LIKE est plus rapide, mais que
REGEXP permet des recherches plus pointues. (dont un site qui obtient un
résultat optimum en combinant LIKE et REGEXP).



@+
Pascal
Avatar
Mickaël Wolff
Denis Beauregard a écrit :

C'est dommage que cette distinction très importante ne soit pas plus
mise en lumière. J'ai lu le manuel de mysql et les sites bidons qui
sont favorisés par l'algorythme farfelu de Google (préférence donnée
au nombre de pages sur un site et aux blogues et non aux sites qui
devraient être la référence). Je n'ai pas vu d'emphase sur la
différence, peut-être parce que tout le monde utilise la valeur par
défaut...



C'est pourtant une fonctionnalité fondamentale des bases de données.
Mais comme toujours, les blogs sont tenus par des débutants qui ont le
temps de faire des articles sans contenu réellement pertinent ;) Mais
j'avoue, la seule ressource qui m'a fait découvrir ces fonctionnalités
est l'excellent « MySQL Cookbook » des éditions O'Reilly.

--
Mickaël Wolff aka Lupus Michaelis
http://lupusmic.org
1 2 3