[spip-dev] bug SQL avec {!critere IN x} avec les tables de liens "objet, id_objet"

Bonjour et bonne année,

Voici un bug bien caché que je ne suis pas du tout arrivé à suivre dans les méandres de criteres.php et composer.php.
Avec un SPIP 2.3.0 le critère {!id_mot ID 1,5} n’arrive pas à fabriquer la bonne sous-requête SQL, il renvoie:

<BOUCLE_artmot(ARTICLES){!id_mot IN 1,5}> :

[…]
AND NOT((articles.id_article IN (
SELECT L1.id_objet, L1.id_objet AS id_article
FROM spip_mots_liens AS L1
WHERE ((L1.id_mot IN (1,5))))))
[…]

Le problème vient du fait que les liens des mots sont maintenant dans une table objet,id_objet.
Mais ce n’est pas un bug nouveau; un SPIP 2.1.2 a déjà du mal avec une boucle:

<BOUCLE_artdoc(ARTICLES){!id_document IN 1,5}> :

[…]
AND NOT((articles.id_article IN (
SELECT L1.id_objet, L1.id_objet AS id_article
FROM spip_documents_liens AS L1
WHERE ((L1.id_document IN (1,5))))))
[…]

J’attendrai plutôt une requête :

[…]
AND NOT((articles.id_article IN (
SELECT L1.id_objet AS id_article
FROM spip_documents_liens AS L1
WHERE ((L1.id_document IN (1,5))
AND (L1.objet=‘article’)))))
[…]

=> 1 seul SELECT : mysql renvoie l’erreur : Erreur SQL 1241 Operand should contain 1 column(s).
=> un WHERE qui réduit les réponses à l’objet de la table principale de la boucle.

Merci,
ArnAud.

Bonjour et bonne année,

Voici un bug bien caché que je ne suis pas du tout arrivé à suivre dans les méandres de criteres.php et composer.php.
Avec un SPIP 2.3.0 le critère {!id_mot ID 1,5} n'arrive pas à fabriquer la bonne sous-requête SQL, il renvoie:

<BOUCLE_artmot(ARTICLES){!id_mot IN 1,5}> :

<BOUCLE_artmot(ARTICLES){id_mot !IN 1,5}>
plutôt ?

Ok, je confirme également ce que tu dis.
Il faut réussir à créer (comme tu as mentionné) :

SELECT L1.id_objet AS id_article
FROM spip_mots_liens AS L1
WHERE L1.id_mot IN (1,5) AND L1.objet=article

Et bien non, ce n'est pas la même chose:
  - la première boucle donne les articles qui n'ont le mot-clefs 1 ou 5
  - la deuxième boucle uniquement les articles ayant au moins un mot clef qui n'est pas 1 ou 5.
Au niveau SQL:
  - la première crée une sous-requête qui sera intégré dans le WHERE sous forme de NOT IN (...)
  - la deuxième crée une jointure INNER JOIN (ce qui fait que seul les articles possédant au moins un mot clef seront listés)

Voir la doc sur spip.net : http://www.spip.net/fr_article3997.html et http://www.spip.net/fr_article4008.html

Salutations,
ArnAud.

effectivement, il y a eu cette variante qui simplifie

C'est un vrai bug introduit par [11469]
qui ne devrait pas introduire 2 fois la même valeur sous 2 noms différents dans un Select.
Ce patach semble marcher:

+++ composer.php (working copy)
@@ -644,9 +644,9 @@
            $newcle = explode('.',$nfrom[4]);
            $newcle = end($newcle);
            if ($newcle!=$oldcle){
- $alias = ", ".$nfrom[4]." AS $oldcle";
+ $alias = $nfrom[4]." AS $oldcle";
            }
- $select = remplacer_jointnul($t . $alias, $select, $e);
+ $select = remplacer_jointnul($alias, $select, $e);

mais je ne suis pas sûr que ça ne pose pas d'autres pbs.
Cédric ?

Committo,Ergo:Sum

Ce correctif corrige effectivement un des problèmes. Mais pas les 2 : l'autre étant qu'il manque «and objet='article'» dans la sous-requête pour filtrer uniquement sur ces liaisons là.

C'est un vrai bug introduit par [11469]
qui ne devrait pas introduire 2 fois la même valeur sous 2 noms différents dans un Select.
Ce patach semble marcher:

..

mais je ne suis pas sûr que ça ne pose pas d'autres pbs.
Cédric ?

Ce correctif corrige effectivement un des problèmes. Mais pas les 2 : l'autre étant qu'il manque «and objet='article'» dans la sous-requête pour filtrer uniquement sur ces liaisons là.

Oui bien vu. Mais en dernière analyse qqch ne va pas dans tout ça.

Si j'écris:

<BOUCLE1(ARTICLES){id_document !IN 1,5}>#ID_ARTICLE</BOUCLE1>

le compilateur fait bien son boulot:

SELECT articles.id_article, articles.lang, articles.titre
FROM SLR_articles AS `articles`
INNER JOIN SLR_documents_liens AS L1 ON ( L1.id_objet = articles.id_article AND L1.objet='article')
WHERE (articles.statut = 'publie')
  AND (articles.date < '2138-01-01 00:00:00')
  AND ((L1.id_document NOT IN (1,5)))
GROUP BY articles.id_article

L'écriture où le "!" est mis au début:

BOUCLE1(ARTICLES){id_document !IN 1,5}>#ID_ARTICLE</BOUCLE1>

produit une requêté complètement différente, et de surcroit buggé.
Je ne comprends pas fondamenetalement pas pourquoi ces 2 écritures ne sont pas considérées comme équivalentes,
c'est complètement contre-intuitif.

Committo,Ergo:Sum

Oui... en fait, l'écriture avec le ! devant est arrivé je ne sais quand pour répondre à un besoin précis.
{id_mot != 3} est différent de {!id_mot = 3} :
- le premier prend tous les mots qui ne sont pas 3 MAIS il y a au moins un mot (c'est ce que faisait naturellement SPIP)
- le second dit qu'on ne veut pas le mot 3, mais qu'il peut y avoir d'autres mots, ou aucun mot : la jointure n'est pas la même.

Voir : http://programmer.spip.org/L-Operateur,122
Et certainement ailleurs sur spip.net

produit une requêté complètement différente, et de surcroit buggé.
Je ne comprends pas fondamenetalement pas pourquoi ces 2 écritures ne sont pas considérées comme équivalentes,
c'est complètement contre-intuitif.

En vérité c'est SQL qui induit des choses contre-intuitives. On parle
de tous ceux qui voulaient "tous les articles sauf ceux liés au mot
numéro 2" et tentaient intuitivement {id_mot!=2}, ce qui signifie en
bonne logique "lié à un mot dont l'id est différent de 2".

En introduisant {!id_mot=2} on pouvait enfin énoncer "n'est pas (lié
au mot 2)", à lire donc plutôt comme suit : {!(id_mot=2)} .

-- Fil

Et ce fut un grand progrés :slight_smile:
puisqu'auparavant on était obligé de faire une boucle doublons préalable
pour en exclure les résultats ensuite.

JL