[spip-dev] Modification de la compilation de jointures en 3.1 ?

Cas concret, une boucle dans le plugin Sélections éditoriales *qui n'a pas bougé* :

(SELECTIONS selections_liens){!orphelins}{auteurs_liens.id_auteur?}

Cette boucle contient *une jointure explicite* demandée. Le compilateur DOIT l'ajouter normalement, ce n'est pas facultatif, on la demande explicitement.

La seconde différence *hors* compilateur qui a changé aussi, c'est que Cédric à modifier {orphelins} pour ajouter une "subquery". Mais ce point n'est pas censé être lié à la jointure, c'est dans une des conditions du "where".

Voici sur les deux versions :

## En SPIP 3.0

SELECT selections.id_selection, 0 AS points, '', L2.id_objet, L2.objet, selections.titre AS titre_rang, selections.titre
FROM spip_selections AS 'selections'
INNER JOIN spip_selections_liens AS L2 ON ( L2.id_selection = selections.id_selection )
WHERE selections.id_selection IN (
SELECT DISTINCT id_selection
FROM spip_selections_liens AS oooo)
GROUP BY selections.id_selection
ORDER BY 0+selections.titre, id_objet

## En SPIP 3.1

SELECT selections.id_selection, 0 AS points, '', L1.id_objet, L1.objet, selections.titre AS titre_rang, selections.titre
FROM spip_selections AS `selections`
INNER JOIN spip_auteurs_liens AS L1 ON ( L1.id_objet = ctions.id_selection AND L1.objet='selection')
WHERE selections.id_selection IN (SELECT * FROM(
SELECT DISTINCT id_selection
FROM spip_selections_liens AS oooo) AS subquery)
GROUP BY selections.id_selection
ORDER BY 0+selections.titre, id_objet

En 3.1, quand on vire {auteurs_liens.id_auteur?}, alors ça remet la bonne jointure "spip_selections_liens". Mais en 3.0 ça marche direct sans rien changer.

a modifié…

J'ai viré la modif de Cédric de "subquery", et comme je le pensais ça ne change rien, ça bug toujours.

Donc c'est bien une modification dans le compilateur du noyau qui provoque une différence de résultat entre 3.0 et 3.1.

Quid ?

(Et ça voudrait dire qu'on a pas assez de tests unitaires, si on arrive à modifier le compilateur sans s’apercevoir qu'on casse des choses…)

Il y a possiblement un lien avec ces commits, non ?

https://core.spip.net/projects/spip/repository/revisions/21875
https://core.spip.net/projects/spip/repository/revisions/21876
https://core.spip.net/projects/spip/repository/revisions/21877

Ces commits ajoutent des jointures que ne faisait pas 3.0.

Or la boucle buguée contient *à la fois*
- une jointure explicite dans le type de boucle (SELECTIONS selections_liens)
- une jointure semi-explicite dans le critère {auteurs_liens.id_auteur?}

Sauf que le deuxième point : il est censé se lancer *uniquement* si ya un #ENV{id_auteur} dans le contexte, puisqu'il est facultatif "?".

Or 1) ça ajoute désormais la jointure alors qu'il n'y a PAS de "id_auteur" dans le contexte
Et 2) ça *oublie* la jointure obligatoire "selections_liens" qui est clairement demandée non facultatif là

Erreur de copier-coller : il n'y a PAS de chaîne coupée, c'est bien :
L1.id_objet = selections.id_selection

Hop,

Hello,

plusieurs remarques sur ton mail.

1/ il y a incomprehension de la notion de jointure explicite. Tu dis "le compilateur DOIT l'ajouter". Non, ce n'est pas comme ça que ça marche. Cela doit se comprendre en "si le compilateur a besoin de champs supplémentaires, il doit regarder dans ces tables"
C'est une nuance de taille.

2/ oui il manque des tests unitaires sur la compilation des boucles, oui il faut les formaliser et les écrire, mais cela dit on sait qu'on a changé le comportement de certaines jointures un peu exotiques qui marchaient par chance en 3.0

3/ oui le commit en cause est très certainement
https://core.spip.net/projects/spip/repository/revisions/21875
qui fait un bugfix sur certains cas où le compilateur doublonnait des jointures car il piochait dans la jointure explicite sans verifier que le champ cherché existait déjà dans les jointures posées

4/ le critère {auteurs_liens.id_auteur?} ne veut pas dire que le compilateur ajoute la jointure uniquement si il y a un id_auteur dans le env, ça ne serait pas possible : la jointure est créer lors de la compilation, le env est connu a l'execution.
En pratique le compilateur ajoute la jointure dans la construction de la boucle (elle est donc toujours là), le critère where est lui ajouté de façon facultative en fonction du env, et l'executeur de requete se charge in fine de nettoyer les jointures et autres critères inutiles.

5/ on ne peut pas debug ta boucle car il faut connaitre son contenu, qui déclenche ou non les jointures. Je présume que ta boucle contient des #OBJET et #ID_OBJET

6/ si tu as bien suivi tu comprends ce qui se passe :
- le compilateur crée la boucle avec jointure sur spip_auteurs
- il voit un #ID_OBJET dans le corps de la boucle
- il cherche le champ dans la requete SQL et le trouve sur la table auteurs_liens
- lors de l'execution sans id_auteur dans le env, la jointure auteurs_liens est conservée car elle sert pour le champ #ID_OBJET

En 3.0 on avait
- le compilateur crée la boucle avec jointure sur spip_auteurs
- il voit un #ID_OBJET dans le corps de la boucle
- il cherche le champ dans les jointures explicites
- il ajoute la jointure sur selections_liens
- lors de l'execution sans id_auteur dans le env, la jointure auteurs_liens est supprimée

C'est pur coup de chance que ta boucle ait marché en 3.0 car il y a ambiguité sur le sens de #ID_OBJET qui peut venir aussi bien de selections_liens que de auteurs_liens, les deux jointures étant explicites dans ta boucle

Peut-être que la solution serait de modifier le sens des jointures explicites pour lui donner celui que tu suppose : une jointure que l'on force, explicitement, avant toute autre analyse de la boucle, quitte à ce qu'elle soit optimisée ensuite si elle ne sert pas.
Mais c'est une modification non mineure a tester en 3.2-dev

Par ailleurs il ne me parait pas judicieux de revenir sur le r21875 car celui ci corrige un vrai bug.

Je vais reflechir aux possibilités, côté compilateur, mais en attendant je vois deux solutions :
1/ faire une première boucle de selection avec un {id_auteur?} et un {doublons} et ensuite une boucle d'utilisation
2/ centrer ta boucle sur selections_liens avec les jointures explicites sur selections et auteurs_liens, mais je ne suis pas certain que id_auteur sera bien interprété dans ce cas, je crains qu'on ait une jointure directe selections_liens - auteurs_liens

6/ si tu as bien suivi tu comprends ce qui se passe :
- le compilateur crée la boucle avec jointure sur spip_auteurs
- il voit un #ID_OBJET dans le corps de la boucle
- il cherche le champ dans la requete SQL et le trouve sur la table
auteurs_liens
- lors de l'execution sans id_auteur dans le env, la jointure
auteurs_liens est conservée car elle sert pour le champ #ID_OBJET

Ok, merci Cédric, je comprends beaucoup mieux !

Mais du coup, l'ordre de priorité ne me parait pas du tout logique.

Peut-être que la solution serait de modifier le sens des jointures
explicites pour lui donner celui que tu suppose : une jointure que l'on
force, explicitement, avant toute autre analyse de la boucle, quitte à
ce qu'elle soit optimisée ensuite si elle ne sert pas.
Mais c'est une modification non mineure a tester en 3.2-dev

Et donc effectivement, je pense que la solution (ou une des solutions), c'est déjà que les jointures *prioritaires* soient celles proposées dans la définition de la boucle (entre parenthèses), *avant* celles possibles par les critères (accolades).

Car en tant que dev de squelettes, cela parait plus logique que ce qu'on a proposé en premier… soit utilisé en premier. D'autant que là ce n'est même pas juste une question d'ordre : mais aussi de sens, car la première utilisation est dans la définition du type de boucle. Intuitivement prioritaire à mon avis.

Je vais reflechir aux possibilités, côté compilateur, mais en attendant
je vois deux solutions :

En attendant j'ai pour le moment viré le critère d'auteurs, qui pour l'instant n'était pas utilisé dans des inclusions de la liste des "sélections liées à des contenus".

Mais à terme on pourrait imaginer que sur les pages des comptes utilisateurs, on affiche les sélections qu'ils gèrent, donc oui il faudra trouver une solution dans ce cas.

Encore merci…
(mais à suivre car pas résolu pour de vrai)

Un récent log témoigne des ténébreuses circonvolutions du moteur de jointure :
<< Boucler directement sur la table GIS_LIENS et non GIS gis_liens sans quoi SPIP 3.1 ajoute un GROUP BY L1.id_gis à la requête SQL, ce qui n'affiche qu'un seul objet lié >>

Un tel usage de la casse me laisse perplexe.
Si vous ne voyez pas ce que je veux dire, imaginez vous une boucle dont le fonctionnement changerait
selon que son nom comporte un nombre pair ou impair de voyelles ?

Plus généralement, c'est souvent à tâton que je fais la recherche de la bonne jointure.

J'ai l'impression que la montée en puissance de l'intelligence des jointures
ne s'est pas accompagnée de l'interface qui permettrait de s'en servir facilement.

C'est devenu comme un moteur de formule 1 dans une carrosserie de 2 chevaux
avec les manettes d'un jouet téléguidé 3-5 ans.

L'interface qui révélerait et rendrait accessible cette puissance, ce serait
une mise à plat (oulala) au moins conceptuelle, l'invention éventuelle
de nouveaux éléments de langage (= lexicaux ou grammaticaux)
qui structureraient le code et rendraient les fonctionnalités plus aisément accessibles...
et finalement une documentation.

JLuc

Hop,

Un récent log témoigne des ténébreuses circonvolutions du moteur de
jointure :
<< Boucler directement sur la table GIS_LIENS et non GIS gis_liens sans
quoi SPIP 3.1 ajoute un GROUP BY L1.id_gis à la requête SQL, ce qui
n'affiche qu'un seul objet lié >>

Merci de remonter le problème :slight_smile:

Un tel usage de la casse me laisse perplexe.
Si vous ne voyez pas ce que je veux dire, imaginez vous une boucle dont
le fonctionnement changerait
selon que son nom comporte un nombre pair ou impair de voyelles ?

Heu, c'est pas nouveau ça, cf :

On utilise les jointures "classiques" dans la boucle. Il faut juste faire attention à respecter la casse du nom des tables car la mise en capitales du nom des tables occasionne une perte de jointure.

Le problème n'est pas la casse, mais plutôt le fait que SPIP 3.1 casse la boucle cité dans le log de commit :stuck_out_tongue:

Ben si, nouveau ou pas, ya un problème de compréhension, plus ou moins intuitive (et tant qu'il n'y a pas une doc hyper claire c'est forcément par l'intuition), de comment ça marche, et de quelles sont les syntaxes correctes : pourquoi dès fois en majuscule, des fois en minuscule, quand est-ce qu'on doit mettre l'un ou l'autre et surtout pourquoi ?…

Il doit y avoir 3 ou 4 personnes seulement qui savent répondre à ça… (et je n'en fais pas partie.)

Hop,

Euh oui j'ai bien vu cet article, qui n'est pas une doc de syntaxe des boucles mais un exemple précis (= un tutoriel quoi).

Et qui date de 2010, et dont les exemples ont encore une différence de plus : cette fois-ci ya le *préfixe* "spip_" ajouté devant *certains* passages… (parfois avec spip_, et le truc suivant sans). Encore une incompréhension de plus à tenter de comprendre avec la casse…

Du coup ce lien m'ajoute encore plus de questions au lieu d'en résoudre.

Une documentation de syntaxe pour moi, c'est quelque chose qui répond à ces questions :

- Que dois-je mettre entre les parenthèses ?
- Si je mets plusieurs mots dans les parenthèses, que signifie le premier, et que signifie les suivants ?
- La casse a-t-elle une importance ?
- Est-ce autorisé pour tous les mots dans la parenthèse ou seulement le premier mot ?
- Si oui, que signifie le fait de mettre ce()s mot(s) en majuscule ?
- Dois-je mettre le préfixe "spip_" ou pas ?
- Si oui, dans quels cas dois-je le mettre et dans quels cas puis-je l'enlever ?

Et possiblement d'autres questions…
Là c'est juste pour ce qui est entre parenthèse, ensuite on peut ajouter d'autres questions sur les jointures, sur leur ordre, et sur la combinaison entre ce qu'on met entre parenthèses et ce qu'on met dans les critères (là c'est l'explication de Cédric hier).

Le fait de donner des exemples précis, ça ne vient qu'en plus, pour illustrer.

J'imagine que si on a des réponses claires à ces questions,
alors on imaginera vite de nouvelles écritures
plus facilement documentables et accessibles.

Par exemple, si l'on parvient à exprimer en termes simples
les variations de fonctionnalités qu'induisent la casse et le préfixage
(à défaut, je vais écrire ici 'jointure_majuscule' et 'jointure_prefixe')
alors on pourra écrire sous la forme de critères plus lisibles :
{jointure_majuscule unetable}{jointure_prefixe uneautretable}

Et ce sera alors un plaisir d'écrire la doc :slight_smile:
(en 2007 déjà j'essayais d'apporter de la clarté avec :
http://contrib.spip.net/Acces-SPIP-aux-tables-externes-et-jointures
mais je ne serai pas capable de dire ce qui est encore valable
et tout ce qu'il manque aujourd'hui )

Et le code restructuré ne sera t il pas plus facile à maintenir ?

Si les bouleversements sont risqués ou si ya des incompatibilités
on peut aussi imaginer une nouvelle syntaxe (éventuellement temporaire) :
<JOINTURE_nomboucle(TABLEs ...){...}>
qui serait une variante "remise à plat" des <BOUCLE_...>

JL

JLuc

Avant de changer le code, d'après moi la priorité ça serait déjà d'arriver à documenter clairement comment ça marche actuellement, sans rien oublier, avec toutes les possibilités (cf au moins les questions précédentes).

Bonjour,

J'aimerais soulever le cas des jointures entre des tables d'une base autre
que celle de spip.

J'ai un fichier de connection educ.php dans config/ qui me permet d'afficher
simplement des tables de la base educ grâce à des boucles du type
BOUCLE(educ:eleves){...}.

Pas besoins d'autre chose que le fichier config/educ.php pour me connecter à
cette base de donnée.

Avec spip3.1 certaines de mes jointures tirées par les cheveux ne marchent
plus j'ai donc ajouté par exemple via le pipeline declarer_tables_objets_sql

    $tables['eleves'] = array(
             'join' => array(
                   "id_eleve"=>"id_eleves,
             ),
     );

pour aider spip à s'en sortire et ça fonctionne très bien: mes jointures
tarabiscotées se font sans problème.

Sauf que dans l'interface privée je me retrouve avec des messages d'erreur
de verifier_crash_tables() qui cherche à vérifier ces tables sur la base
spip.

Y'aurait-il une autre façon d'expliciter des jointures sans ajouter de
tables à tables_principales?

Peux-t-on prévoire un marqueur soit pour exclure ces tables soit pour
prendre en compte leur fichier de connexion spécifique?

Merci pour vos retours