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; |
} |
} |
} |