Skip to content

Ricerca globale restituisce un solo risultato quando il modulo ha un segmento con filtro non vuoto (getAdditionalsQuery concatenata dopo GROUP BY) #1852

Description

@lincetto

Descrizione

La ricerca globale (barra di ricerca in alto → ajax_search.php) restituisce un solo risultato per un modulo anche quando i record che combaciano sono molti, se il modulo ha un segmento (o condizione aggiuntiva) con filtro non vuoto in posizione WHERE. Con la configurazione di default il problema si presenta sempre, perché il segmento predefinito "Tutti" ha filtro 1=1.

Comportamento atteso

La ricerca globale restituisce tutti i record che combaciano con il termine cercato.

Comportamento osservato

Restituisce esattamente 1 record, qualunque sia il termine. Con un termine che combacia con un solo articolo, quello viene trovato correttamente; con un termine che combacia con più articoli, ne esce sempre e solo il primo secondo l'ordinamento. La risposta AJAX contiene un solo oggetto.

Esempio: cercando bluet (molti articoli con "Bluetooth" in descrizione) esce un solo articolo; cercando maxi2 (un solo articolo) esce quell'articolo — che però contiene anch'esso "Bluetooth" e quindi doveva comparire anche nella ricerca bluet.

Causa

In modules/articoli/ajax/search.php la query viene chiusa così (righe ~53-55, identiche in 2.9 → 2.11):

$query .= ') GROUP BY `mg_articoli`.`id`';
$query .= Modules::getAdditionalsQuery(Module::where('name', 'Articoli')->first()->id);

getAdditionalsQuery() invocata senza parametro $type fonde le condizioni WHR e HVN in un'unica stringa AND <condizioni>, che viene concatenata dopo il GROUP BY. È un errore di posizionamento:

  • È un baco per le condizioni in posizione WHERE. Una condizione WHERE deve stare prima del GROUP BY; concatenandola dopo, entra a far parte dell'espressione di raggruppamento. GROUP BY \mg_articoli`.`id` AND 1=1viene interpretato da MySQL comeGROUP BY (`id` AND 1=1): poiché idè sempre ≠ 0, l'espressione vale sempre1, tutte le righe finiscono in un unico gruppo e con SELECT * viene restituita **una sola riga**. Nota che basta anche una condizione no-op (1=1`, il filtro del segmento "Tutti") a innescare il collasso — per questo il problema si manifesta praticamente su ogni installazione con un segmento WHERE non vuoto.
  • Per le condizioni in posizione HAVING il "dopo il GROUP BY" sarebbe la regione corretta, quindi la rottura da WHERE mal posizionata non si presenterebbe; resta però che il codice concatena AND cond senza la keyword HAVING, quindi anche una condizione HVN finirebbe erroneamente dentro l'espressione del GROUP BY.

In pratica il search.php non usa il meccanismo di instradamento per posizione che OSM applica già altrove tramite Modules::replaceAdditionals() (placeholder 1=1 per la WHERE, 2=2 per la HAVING).

Perché il problema è poco evidente

La query mostrata come "Query risultante" nell'editor dei segmenti non è quella eseguita dalla ricerca globale. Sono percorsi diversi:

  • Editor segmenti (modules/segmenti/edit.php) e lista/DataTables (src/Util/Query.php, src/API/Controllers/DataTablesController.php) applicano il segmento tramite Modules::replaceAdditionals(), che inserisce le condizioni nei placeholder 1=1 (WHERE) e 2=2 (HAVING) della query principale del modulo → SQL corretto. Per questo l'anteprima sembra a posto e il filtro della lista funziona.
  • La ricerca globale (modules/*/ajax/search.php) non usa replaceAdditionals: costruisce una query separata e concatena getAdditionalsQuery() dopo il GROUP BY. È questa la query realmente eseguita, ed è quella malformata.

Anteprima corretta, lista funzionante e ricerca globale rotta convivono perché sono tre consumatori con due meccanismi diversi.

Come riprodurre

  1. Modulo Articoli con il segmento predefinito "Tutti" (filtro 1=1, posizione WHERE) — configurazione di default.
  2. Avere più articoli che combaciano con un termine (es. più articoli con "Bluetooth" in descrizione).
  3. Cercare quel termine dalla barra di ricerca globale.
  4. Risultato: un solo articolo invece di tutti quelli che combaciano.

Verifica lato DB — questa query restituisce correttamente tutti i match:

SELECT a.id, a.codice, l.title
FROM mg_articoli a
LEFT JOIN mg_articoli_lang l
  ON a.id = l.id_record AND l.id_lang = (SELECT id FROM zz_langs WHERE predefined = 1)
WHERE a.deleted_at IS NULL
  AND (1=0 OR l.title LIKE '%bluet%' OR a.codice LIKE '%bluet%')
GROUP BY a.id;

Aggiungendo AND 1=1 dopo GROUP BY a.id il risultato collassa a 1 riga: è la dimostrazione del problema.

Moduli coinvolti

Stesso pattern (getAdditionalsQuery() concatenata dopo il GROUP BY):

  • modules/articoli/ajax/search.php
  • modules/contratti/ajax/search.php
  • modules/interventi/ajax/search.php
  • modules/preventivi/ajax/search.php

Proposta di correzione

Instradare le condizioni per posizione, riusando lo stesso meccanismo della query principale. Soluzione allineata a replaceAdditionals() (gestisce correttamente sia WHERE che HAVING):

$query .= ') AND 1=1 GROUP BY `mg_articoli`.`id` HAVING 2=2';
$query = Modules::replaceAdditionals(Module::where('name', 'Articoli')->first()->id, $query);

In alternativa, come fix minimale che copre il solo caso WHERE (quello che causa la regressione), spostare solo le condizioni WHR prima del GROUP BY usando il tipo esplicito:

$query .= ')';
$query .= Modules::getAdditionalsQuery(Module::where('name', 'Articoli')->first()->id, 'WHR');
$query .= ' GROUP BY `mg_articoli`.`id`';

Da applicare la stessa correzione agli altri moduli con lo stesso pattern.

Ambiente

  • OSM: 2.11-beta

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions