Subversion Repositories eFlore/Applications.del

Compare Revisions

Ignore whitespace Rev 1485 → Rev 1486

/trunk/services/modules/0.1/images/ListeImages2.php
53,14 → 53,14
*
* MySQL sux:
* EXPLAIN SELECT id_image FROM v_del_image vdi WHERE vdi.id_image IN (SELECT ce_image FROM del_image_tag WHERE actif = 1 LIMIT 1);
* MySQL doesn't yet support 'LIMIT & IN/ALL/ANY/SOME subquery
* MySQL doesn't yet support 'LIMIT & IN/ALL/ANY/SOME subquery
* EXPLAIN SELECT * FROM del_image WHERE id_image IN (SELECT 3);
* PRIMARY
* PRIMARY
* EXPLAIN SELECT * FROM del_image WHERE id_image IN (SELECT MIN(3));
* DEPENDENT SUBQUERY ... ... ... mwarf !
* DEPENDENT SUBQUERY ... ... ... mwarf !
* EXPLAIN SELECT id_image FROM v_del_image vdi WHERE vdi.id_image IN (SELECT ce_image FROM del_image_tag WHERE actif = 1);
* 5.5: MATERIALIZED del_image_tag ALL ce_image NULL NULL NULL 38276 Using where
* 5.1: DEPENDENT SUBQUERY del_image_tag index_subquery ce_image ce_image 8 func 1 Using where
* 5.5: MATERIALIZED del_image_tag ALL ce_image NULL NULL NULL 38276 Using where
* 5.1: DEPENDENT SUBQUERY del_image_tag index_subquery ce_image ce_image 8 func 1 Using where
* FORCE INDEX/IGNORE INDEX semble incapable de résoudre le problème de l'optimiseur MySQL
*
*/
87,9 → 87,9
static $parametres_autorises = array('protocole', 'masque.tag_cel', 'masque.tag_pictoflora', 'masque.milieu');
 
static $default_params = array('navigation.depart' => 0, 'navigation.limite' => 10,
'tri' => 'date_transmission', 'ordre' => 'desc',
// spécifiques à PictoFlora:
'format' => 'XL');
'tri' => 'date_transmission', 'ordre' => 'desc',
// spécifiques à PictoFlora:
'format' => 'XL');
 
static $default_proto = 3; // proto par défaut: capitalisation d'img (utilisé uniquement pour tri=(tags|votes))
 
152,14 → 152,14
// Évitons ce genre de chose pour PictoFlora et les risques de conflits avec masque.tag
// même si ceux-ci sont improbables (pas d'<input> pour cela).
$params_ip = ListeObservations::requestFilterParams($parametres,
array_diff(ListeObservations::$parametres_autorises,
array('masque.type')),
$this->conteneur);
array_diff(ListeObservations::$parametres_autorises,
array('masque.type')),
$this->conteneur);
 
// notre propre filtrage sur l'INPUT
$params_pf = self::requestFilterParams($parametres,
array_merge(ListeObservations::$parametres_autorises,
self::$parametres_autorises));
array_merge(ListeObservations::$parametres_autorises,
self::$parametres_autorises));
 
/* filtrage des tags + sémantique des valeurs multiples:
Lorsqu'on utilise masque.tag* pour chercher des tags, ils sont
168,9 → 168,9
$params_pf['masque.tag_pictoflora'] = self::buildTagsAST(@$parametres['masque.tag_pictoflora'], 'OR', ',');
 
$params = array_merge(ListeObservations::$default_params, // paramètre par défaut Identiplante
self::$default_params, // paramètres par défaut PictoFlora
$params_ip, // les paramètres passés, traités par Identiplante
$params_pf); // les paramètres passés, traités par PictoFlora
self::$default_params, // paramètres par défaut PictoFlora
$params_ip, // les paramètres passés, traités par Identiplante
$params_pf); // les paramètres passés, traités par PictoFlora
 
// XXX: temp tweak
/* $this->conteneur->setParametre('url_images', sprintf($this->conteneur->getParametre('url_images'),
193,12 → 193,12
if(!$idobs_tab) {
$resultat = new ResultatService();
$resultat->corps = array('entete' => ListeObservations::makeJSONHeader(0, $params, Config::get('url_service')),
'resultats' => array());
'resultats' => array());
return $resultat;
/*
header('HTTP/1.0 404 Not Found');
// don't die (phpunit)
throw(new Exception()); */
header('HTTP/1.0 404 Not Found');
// don't die (phpunit)
throw(new Exception()); */
}
 
 
209,12 → 209,12
$liaisons = self::chargerImages($db, $idobs);
 
/*
// Q&D
$images = array();
$o = new Observation($this->conteneur);
foreach($idobs as $i) {
$images[$i] = $o->consulter(array($i), array('justthrow' => 1));
}
// Q&D
$images = array();
$o = new Observation($this->conteneur);
foreach($idobs as $i) {
$images[$i] = $o->consulter(array($i), array('justthrow' => 1));
}
*/
list($images, $images_keyed_by_id_image) = ListeObservations::reformateImagesDoubleIndex(
$liaisons,
235,10 → 235,10
// les deux masques de tags sont transformés en AST dans le processus de construction de la requête.
// Reprenous les paramètres originaux non-nettoyés (ils sont valables car le nettoyage est déterministe)
$params_header = array_merge($params, array_filter(array('masque.tag_cel' => @$parametres['masque.tag_cel'],
'masque.tag_pictoflora' => @$parametres['masque.tag_pictoflora'])));
'masque.tag_pictoflora' => @$parametres['masque.tag_pictoflora'])));
$resultat = new ResultatService();
$resultat->corps = array('entete' => ListeObservations::makeJSONHeader($total, $params_header, Config::get('url_service')),
'resultats' => $images);
'resultats' => $images);
return $resultat;
}
 
303,16 → 303,16
(c'est un *vote* qui porte sur un protocole et non l'image elle-même) */
/* TODO: perf problème:
1) SQL_CALC_FOUND_ROWS: fixable en:
- dissociant le comptage de la récup d'id + javascript async
- ou ne rafraîchir le total *que* pour les requête impliquant un changement de pagination
(paramètre booléen "with-total" par exemple)
- dissociant le comptage de la récup d'id + javascript async
- ou ne rafraîchir le total *que* pour les requête impliquant un changement de pagination
(paramètre booléen "with-total" par exemple)
2) jointure forcées: en utilisant `del_imagè`, nous forçons les 2 premiers
JOIN sur cel_obs_images et cel_obs pour filtrer sur "transmission".
Dénormaliser cette valeur et l'intégrer à `cel_images` ferait économiser cette couteuse
jointure, ... lorsqu'aucun masque portant sur `cel_obs` n'est utilisé
JOIN sur cel_obs_images et cel_obs pour filtrer sur "transmission".
Dénormaliser cette valeur et l'intégrer à `cel_images` ferait économiser cette couteuse
jointure, ... lorsqu'aucun masque portant sur `cel_obs` n'est utilisé
3) non-problème: l'ordre des joins est forcé par l'usage de la vue:
(cel_images/cel_obs_images/cel_obs/del_image_stat)
Cependant c'est à l'optimiseur de définir son ordre préféré. */
(cel_images/cel_obs_images/cel_obs/del_image_stat)
Cependant c'est à l'optimiseur de définir son ordre préféré. */
if($p['tri'] == 'votes') {
// $p['protocole'] *est* défini (cf requestFilterParams())
// petite optimisation: INNER JOIN si ordre DESC car les 0 à la fin
320,14 → 320,14
// pas de group by nécessaire pour cette jointure
// PRIMARY KEY (`ce_image`, `ce_protocole`)
$req['join'][] = sprintf('INNER JOIN del_image_stat dis'.
' ON vdi.id_image = dis.ce_image'.
' AND dis.ce_protocole = %d',
$p['protocole']);
' ON vdi.id_image = dis.ce_image'.
' AND dis.ce_protocole = %d',
$p['protocole']);
} else {
$req['join'][] = sprintf('LEFT JOIN del_image_stat dis'.
' ON vdi.id_image = dis.ce_image'.
' AND dis.ce_protocole = %d',
$p['protocole']);
' ON vdi.id_image = dis.ce_image'.
' AND dis.ce_protocole = %d',
$p['protocole']);
// nécessaire (dup ce_image dans del_image_stat)
$req['groupby'][] = 'vdi.id_observation';
}
335,7 → 335,7
 
if($p['tri'] == 'tags') {
$req['join'][] = sprintf('%s JOIN del_image_stat dis ON vdi.id_image = dis.ce_image',
($p['ordre'] == 'desc') ? 'INNER' : 'LEFT');
($p['ordre'] == 'desc') ? 'INNER' : 'LEFT');
// nécessaire (dup ce_image dans del_image_stat)
$req['groupby'][] = 'vdi.id_observation';
}
348,14 → 348,14
// et auquel cas laisser au client le choix du couteux "%" ?
$tags = $p['masque.tag_cel']['AND'];
array_walk($tags, create_function('&$val, $k, $db',
'$val = sprintf("CONCAT(vdi.mots_cles_texte,vdi.i_mots_cles_texte) LIKE %s",
$db->proteger("%".$val."%"));'),
$db);
'$val = sprintf("CONCAT(vdi.mots_cles_texte,vdi.i_mots_cles_texte) LIKE %s",
$db->proteger("%".$val."%"));'),
$db);
$req['where'][] = '(' . implode(' AND ', $tags) . ')';
}
else {
$req['where'][] = sprintf("CONCAT(vdi.mots_cles_texte,vdi.i_mots_cles_texte) REGEXP %s",
$db->proteger(implode('|', $p['masque.tag_cel']['OR'])));
$db->proteger(implode('|', $p['masque.tag_cel']['OR'])));
}
}
 
366,21 → 366,54
/* $req['join'][] = 'LEFT JOIN del_image_tag dit ON dit.ce_image = vdi.id_image';
$req['where'][] = 'dit.actif = 1'; */
 
 
 
// ==== commenté pour l'instant pour cause de soucis d'optimiseur MySQL (cf commentaire en intro) ====
/*
if(isset($p['masque.tag_pictoflora']['AND'])) {
// optimsation: en cas de "AND" on sort() l'input et le GROUP_CONCAT()
// donc nous utilisons des ".*" plutôt que de multiples conditions et "|"
sort($p['masque.tag_pictoflora']['AND']);
$req['where'][] = sprintf("vdi.id_image IN (SELECT ce_image FROM del_image_tag WHERE actif = 1".
" GROUP BY ce_image".
" HAVING GROUP_CONCAT(tag_normalise ORDER BY tag_normalise) REGEXP %s)",
$db->proteger(implode('.*', $p['masque.tag_pictoflora']['AND'])));
}
else {
$req['where'][] = sprintf("vdi.id_image IN (SELECT ce_image FROM del_image_tag WHERE actif = 1".
" GROUP BY ce_image".
" HAVING GROUP_CONCAT(tag_normalise) REGEXP %s)",
$db->proteger(implode('|', $p['masque.tag_pictoflora']['OR'])));
}
*/
 
// ==== XXX: puisque on est bassiné par cette "DEPENDENT SUBQUERY", nous la faisons donc indépendemment ====
if(isset($p['masque.tag_pictoflora']['AND'])) {
// optimsation: en cas de "AND" on sort() l'input et le GROUP_CONCAT()
// donc nous utilisons des ".*" plutôt que de multiples conditions et "|"
sort($p['masque.tag_pictoflora']['AND']);
$req['where'][] = sprintf("vdi.id_image IN (SELECT ce_image FROM del_image_tag WHERE actif = 1".
" GROUP BY ce_image".
" HAVING GROUP_CONCAT(tag_normalise ORDER BY tag_normalise) REGEXP %s)",
$db->proteger(implode('.*', $p['masque.tag_pictoflora']['AND'])));
 
// plutôt que db->connexion->query->fetchColumn(), une API pourrie nous oblige à ...
$ids = $db->recupererTous(sprintf(
"SELECT ce_image FROM del_image_tag WHERE actif = 1".
" GROUP BY ce_image".
" HAVING GROUP_CONCAT(tag_normalise ORDER BY tag_normalise) REGEXP %s",
$db->proteger(implode('.*', $p['masque.tag_pictoflora']['AND']))));
// puis:
$ids = array_map(create_function('$e', 'return $e["ce_image"];'), $ids);
if($ids) $req['where'][] = sprintf("vdi.id_image IN (%s)", implode(',', $ids));
 
}
else {
$req['where'][] = sprintf("vdi.id_image IN (SELECT ce_image FROM del_image_tag WHERE actif = 1".
" GROUP BY ce_image".
" HAVING GROUP_CONCAT(tag_normalise) REGEXP %s)",
$db->proteger(implode('|', $p['masque.tag_pictoflora']['OR'])));
$ids = $db->recupererTous(sprintf(
"SELECT ce_image FROM del_image_tag WHERE actif = 1".
" GROUP BY ce_image".
" HAVING GROUP_CONCAT(tag_normalise) REGEXP %s",
$db->proteger(implode('|', $p['masque.tag_pictoflora']['OR']))));
$ids = array_map(create_function('$e', 'return $e["ce_image"];'), $ids);
if($ids) $req['where'][] = sprintf("vdi.id_image IN (%s)", implode(',', $ids));
}
 
}
}
 
411,13 → 444,13
$image_fields = Observation::sqlFieldsToAlias(self::$mappings['images'], NULL);
return $db->recupererTous(sprintf('SELECT '.
' CONCAT(id_image, "-", id_observation) AS jsonindex,'.
' %1$s, %2$s FROM v_del_image '.
' WHERE %3$s'.
' -- %4$s',
$obs_fields, $image_fields,
sprintf('id_image IN (%s)', implode(',', $idImg)),
__FILE__ . ':' . __LINE__));
' CONCAT(id_image, "-", id_observation) AS jsonindex,'.
' %1$s, %2$s FROM v_del_image '.
' WHERE %3$s'.
' -- %4$s',
$obs_fields, $image_fields,
sprintf('id_image IN (%s)', implode(',', $idImg)),
__FILE__ . ':' . __LINE__));
 
}
 
425,30 → 458,30
de manière identique à la seule différence que:
1) ils sont combinés par des "OU" logiques plutôt que des "ET".
2) les tags sont traités différemment pour conserver la compatibilité avec l'utilisation historique:
Tous les mots-clefs doivent matcher et sont séparés par des espaces
(dit autrement, str_replace(" ", ".*", $mots-clefs-requête) =~ $mots-clefs-mysql)
Tous les mots-clefs doivent matcher et sont séparés par des espaces
(dit autrement, str_replace(" ", ".*", $mots-clefs-requête) =~ $mots-clefs-mysql)
Pour plus d'information: ListeObservations::sqlAddMasqueConstraint() */
static function sqlAddMasqueConstraint($p, $db, &$req, Conteneur $c = NULL) {
if(!empty($p['masque'])) {
$or_params = array('masque.auteur' => $p['masque'],
'masque.departement' => $p['masque'],
'masque.commune' => $p['masque'], // TODO/XXX ?
'masque.id_zone_geo' => $p['masque'],
'masque.departement' => $p['masque'],
'masque.commune' => $p['masque'], // TODO/XXX ?
'masque.id_zone_geo' => $p['masque'],
 
/* tous-deux remplacent masque.tag
mais sont traité séparément des requestFilterParams() */
// 'masque.tag_cel' => $p['masque'],
// 'masque.tag_pictoflora' => $p['masque'],
/* tous-deux remplacent masque.tag
mais sont traité séparément des requestFilterParams() */
// 'masque.tag_cel' => $p['masque'],
// 'masque.tag_pictoflora' => $p['masque'],
 
'masque.ns' => $p['masque'],
'masque.famille' => $p['masque'],
'masque.date' => $p['masque'],
'masque.genre' => $p['masque'],
'masque.milieu' => $p['masque'],
'masque.ns' => $p['masque'],
'masque.famille' => $p['masque'],
'masque.date' => $p['masque'],
'masque.genre' => $p['masque'],
'masque.milieu' => $p['masque'],
 
// tri est aussi nécessaire car affecte les contraintes de JOIN
'tri' => $p['tri'],
'ordre' => $p['ordre']);
// tri est aussi nécessaire car affecte les contraintes de JOIN
'tri' => $p['tri'],
'ordre' => $p['ordre']);
 
$or_masque = array_merge(
ListeObservations::requestFilterParams($or_params, NULL, $c /* pour masque.departement */),
552,8 → 585,8
 
if($additional_sep) {
array_walk($words,
create_function('&$v, $k, $sep', '$v = preg_split("/".$sep."/", $v, -1, PREG_SPLIT_NO_EMPTY);'),
$additional_sep);
create_function('&$v, $k, $sep', '$v = preg_split("/".$sep."/", $v, -1, PREG_SPLIT_NO_EMPTY);'),
$additional_sep);
}
$words = self::array_flatten($words);
$words = array_map('trim', $words);
594,4 → 627,4
}
return $arr;
}
}
}