From 830a12ef82cbaf7d13955765d91a5f789126ac70 Mon Sep 17 00:00:00 2001 From: Rom1-B Date: Thu, 7 Aug 2025 13:55:00 +0200 Subject: [PATCH 1/4] Optimize counts in search queries --- src/Glpi/Search/Provider/SQLProvider.php | 118 ++++++++++++++++++----- src/Search.php | 7 +- 2 files changed, 97 insertions(+), 28 deletions(-) diff --git a/src/Glpi/Search/Provider/SQLProvider.php b/src/Glpi/Search/Provider/SQLProvider.php index 4c638d63e37..4d8da4df8c6 100644 --- a/src/Glpi/Search/Provider/SQLProvider.php +++ b/src/Glpi/Search/Provider/SQLProvider.php @@ -540,10 +540,10 @@ public static function getSelectCriteria(string $itemtype, int $ID, bool $meta = switch ($opt["datatype"]) { case "count": return array_merge([ - QueryFunction::count( - expression: "$table$addtable.$field", - distinct: true, - alias: $NAME + QueryFunction::ifnull( + expression: new QueryExpression($DB::quoteName("{$table}{$addtable}.counter")), + value: new QueryExpression($DB::quoteValue('0')), + alias: $NAME, ), ], $ADDITONALFIELDS); @@ -2704,6 +2704,7 @@ private static function parseJoinString(string $raw_joins): array * @param class-string|'' $meta_type Meta type table (default 0) * @param array $joinparams Array join parameters (condition / joinbefore...) * @param string $field Field to display (needed for translation join) (default '') + * @param boolean $is_counter Is it a counter field? (default false) * * @return array Left join string **/ @@ -2716,7 +2717,8 @@ public static function getLeftJoinCriteria( bool $meta = false, string $meta_type = '', array $joinparams = [], - string $field = '' + string $field = '', + bool $is_counter = false ): array { /** @var DBmysql $DB */ global $DB; @@ -2966,18 +2968,44 @@ public static function getLeftJoinCriteria( case 'child': $linkfield = $joinparams['linkfield'] ?? getForeignKeyFieldForTable($cleanrt); - // Child join - $child_join = [ - 'LEFT JOIN' => [ - "$new_table$AS" => [ - 'ON' => [ - $rt => 'id', - $nt => $linkfield, + if ($is_counter) { + $subquery = new QuerySubQuery( + [ + 'SELECT' => [ + $linkfield, + new QueryExpression("COUNT(*)", "counter"), ], + 'FROM' => $new_table, + 'GROUPBY' => $linkfield, ], - ], - ]; - $append_join_criteria($child_join['LEFT JOIN']["$new_table$AS"]['ON'], $add_criteria); + $nt + ); + + $child_join = [ + 'LEFT JOIN' => [ + [ + 'TABLE' => $subquery, + 'FKEY' => [ + $rt => 'id', + $nt => $linkfield, + ], + ], + ], + ]; + } else { + // Child join + $child_join = [ + 'LEFT JOIN' => [ + "$new_table$AS" => [ + 'ON' => [ + $rt => 'id', + $nt => $linkfield, + ], + ], + ], + ]; + $append_join_criteria($child_join['LEFT JOIN']["$new_table$AS"]['ON'], $add_criteria); + } $specific_leftjoin_criteria = array_merge_recursive($specific_leftjoin_criteria, $child_join); break; @@ -3142,18 +3170,55 @@ public static function getLeftJoinCriteria( break; default: - // Standard join - $standard_join = [ - 'LEFT JOIN' => [ - "$new_table$AS" => [ - 'ON' => [ - $rt => $linkfield, - $nt => 'id', + if ($is_counter) { + $leftjoin = $before_criteria ?? []; + $leftjoin['LEFT JOIN']["$new_table$AS"] = [ + 'ON' => [ + $rt => $linkfield, + $nt => 'id', + ], + ]; + $leftjoin['INNER JOIN'] = $leftjoin['LEFT JOIN']; + unset($leftjoin['LEFT JOIN']); + $subquery = [ + 'SELECT' => [ + "$ref_table.id", + new QueryExpression("COUNT(DISTINCT $nt.id)", "counter"), + ], + 'FROM' => $ref_table, + 'GROUPBY' => "$ref_table.id", + ] + $leftjoin; + $subtable = new QuerySubQuery( + $subquery, + $nt + ); + + $standard_join = [ + 'LEFT JOIN' => [ + [ + 'TABLE' => $subtable, + 'FKEY' => [ + $ref_table => 'id', + $nt => 'id', + ], ], ], - ], - ]; - $append_join_criteria($standard_join['LEFT JOIN']["$new_table$AS"]['ON'], $add_criteria); + ]; + $before_criteria = []; + } else { + // Standard join + $standard_join = [ + 'LEFT JOIN' => [ + "$new_table$AS" => [ + 'ON' => [ + $rt => $linkfield, + $nt => 'id', + ], + ], + ], + ]; + $append_join_criteria($standard_join['LEFT JOIN']["$new_table$AS"]['ON'], $add_criteria); + } $specific_leftjoin_criteria = array_merge_recursive($specific_leftjoin_criteria, $standard_join); $transitemtype = getItemTypeForTable($new_table); if (Session::haveTranslations($transitemtype, $field)) { @@ -4185,7 +4250,8 @@ public static function constructSQL(array &$data) false, '', $searchopt[$val]["joinparams"], - $searchopt[$val]["field"] + $searchopt[$val]["field"], + (($searchopt[$val]['datatype'] ?? '') == 'count') ); } } diff --git a/src/Search.php b/src/Search.php index 42fe3a98c62..a6f78de686a 100644 --- a/src/Search.php +++ b/src/Search.php @@ -701,6 +701,7 @@ public static function addDefaultJoin($itemtype, $ref_table, array &$already_lin * @param string $meta_type Meta item type * @param array $joinparams Array join parameters (condition / joinbefore...) * @param string $field Field to display (needed for translation join) (default '') + * @param bool $is_counter Is it a counter join? (default false) * * @return string Left join string **/ @@ -713,7 +714,8 @@ public static function addLeftJoin( $meta = false, $meta_type = '', $joinparams = [], - $field = '' + $field = '', + bool $is_counter = false ) { /** @var DBmysql $DB */ global $DB; @@ -726,7 +728,8 @@ public static function addLeftJoin( (bool) $meta, (string) $meta_type, $joinparams, - $field + $field, + $is_counter ); $iterator = new DBmysqlIterator($DB); $iterator->buildQuery([ From 0ad96519f99eb41cbc834895389794a75dc1a922 Mon Sep 17 00:00:00 2001 From: Rom1-B Date: Fri, 8 Aug 2025 14:16:01 +0200 Subject: [PATCH 2/4] use_join_subquery --- src/Glpi/Search/Provider/SQLProvider.php | 94 ++++++++++++++++++------ src/Search.php | 6 +- src/User.php | 3 + 3 files changed, 78 insertions(+), 25 deletions(-) diff --git a/src/Glpi/Search/Provider/SQLProvider.php b/src/Glpi/Search/Provider/SQLProvider.php index 4d8da4df8c6..7f76df3b27c 100644 --- a/src/Glpi/Search/Provider/SQLProvider.php +++ b/src/Glpi/Search/Provider/SQLProvider.php @@ -539,11 +539,20 @@ public static function getSelectCriteria(string $itemtype, int $ID, bool $meta = if (isset($opt["datatype"])) { switch ($opt["datatype"]) { case "count": + if ($opt["use_join_subquery"] ?? false) { + return array_merge([ + QueryFunction::ifnull( + expression: new QueryExpression($DB::quoteName("{$table}{$addtable}.counter")), + value: new QueryExpression($DB::quoteValue('0')), + alias: $NAME, + ), + ], $ADDITONALFIELDS); + } return array_merge([ - QueryFunction::ifnull( - expression: new QueryExpression($DB::quoteName("{$table}{$addtable}.counter")), - value: new QueryExpression($DB::quoteValue('0')), - alias: $NAME, + QueryFunction::count( + expression: "$table$addtable.$field", + distinct: true, + alias: $NAME ), ], $ADDITONALFIELDS); @@ -2704,7 +2713,6 @@ private static function parseJoinString(string $raw_joins): array * @param class-string|'' $meta_type Meta type table (default 0) * @param array $joinparams Array join parameters (condition / joinbefore...) * @param string $field Field to display (needed for translation join) (default '') - * @param boolean $is_counter Is it a counter field? (default false) * * @return array Left join string **/ @@ -2718,7 +2726,7 @@ public static function getLeftJoinCriteria( string $meta_type = '', array $joinparams = [], string $field = '', - bool $is_counter = false + bool $use_join_subquery = false ): array { /** @var DBmysql $DB */ global $DB; @@ -2968,7 +2976,7 @@ public static function getLeftJoinCriteria( case 'child': $linkfield = $joinparams['linkfield'] ?? getForeignKeyFieldForTable($cleanrt); - if ($is_counter) { + if ($use_join_subquery) { $subquery = new QuerySubQuery( [ 'SELECT' => [ @@ -3085,22 +3093,64 @@ public static function getLeftJoinCriteria( } // Itemtype join - $itemtype_join = [ - 'LEFT JOIN' => [ - "$new_table$AS" => [ - 'ON' => [ - $rt => 'id', - $nt => "{$addmain}{$items_id_column}", - [ - 'AND' => [ - "$nt.{$addmain}{$itemtype_column}" => $used_itemtype, + if ($use_join_subquery) { + $leftjoin = $before_criteria; + $leftjoin['LEFT JOIN']["$new_table$AS"] = [ + 'ON' => [ + $rt => 'id', + $nt => "{$addmain}{$items_id_column}", + [ + 'AND' => [ + "$nt.{$addmain}{$itemtype_column}" => $used_itemtype, + ], + ], + ], + ]; + $leftjoin['INNER JOIN'] = $leftjoin['LEFT JOIN']; + unset($leftjoin['LEFT JOIN']); + $subquery = [ + 'SELECT' => [ + "$ref_table.id", + new QueryExpression("COUNT(DISTINCT $nt.id)", "counter"), + ], + 'FROM' => $ref_table, + 'GROUPBY' => "$ref_table.id", + ] + $leftjoin; + $subtable = new QuerySubQuery( + $subquery, + $nt + ); + + $itemtype_join = [ + 'LEFT JOIN' => [ + [ + 'TABLE' => $subtable, + 'FKEY' => [ + $ref_table => 'id', + $nt => 'id', + ], + ], + ], + ]; + $before_criteria = []; + } else { + $itemtype_join = [ + 'LEFT JOIN' => [ + "$new_table$AS" => [ + 'ON' => [ + $rt => 'id', + $nt => "{$addmain}{$items_id_column}", + [ + 'AND' => [ + "$nt.{$addmain}{$itemtype_column}" => $used_itemtype, + ], ], ], ], ], - ], - ]; - $append_join_criteria($itemtype_join['LEFT JOIN']["$new_table$AS"]['ON'], $add_criteria); + ]; + $append_join_criteria($itemtype_join['LEFT JOIN']["$new_table$AS"]['ON'], $add_criteria); + } $specific_leftjoin_criteria = array_merge_recursive($specific_leftjoin_criteria, $itemtype_join); break; @@ -3170,8 +3220,8 @@ public static function getLeftJoinCriteria( break; default: - if ($is_counter) { - $leftjoin = $before_criteria ?? []; + if ($use_join_subquery) { + $leftjoin = $before_criteria; $leftjoin['LEFT JOIN']["$new_table$AS"] = [ 'ON' => [ $rt => $linkfield, @@ -4251,7 +4301,7 @@ public static function constructSQL(array &$data) '', $searchopt[$val]["joinparams"], $searchopt[$val]["field"], - (($searchopt[$val]['datatype'] ?? '') == 'count') + ($searchopt[$val]['use_join_subquery'] ?? false) ); } } diff --git a/src/Search.php b/src/Search.php index a6f78de686a..010b5efa200 100644 --- a/src/Search.php +++ b/src/Search.php @@ -701,7 +701,7 @@ public static function addDefaultJoin($itemtype, $ref_table, array &$already_lin * @param string $meta_type Meta item type * @param array $joinparams Array join parameters (condition / joinbefore...) * @param string $field Field to display (needed for translation join) (default '') - * @param bool $is_counter Is it a counter join? (default false) + * @param bool $use_join_subquery Use a subquery for the join (default false) * * @return string Left join string **/ @@ -715,7 +715,7 @@ public static function addLeftJoin( $meta_type = '', $joinparams = [], $field = '', - bool $is_counter = false + $use_join_subquery = false ) { /** @var DBmysql $DB */ global $DB; @@ -729,7 +729,7 @@ public static function addLeftJoin( (string) $meta_type, $joinparams, $field, - $is_counter + $use_join_subquery ); $iterator = new DBmysqlIterator($DB); $iterator->buildQuery([ diff --git a/src/User.php b/src/User.php index eae4c1062d1..8e70381865f 100644 --- a/src/User.php +++ b/src/User.php @@ -3550,6 +3550,7 @@ public function rawSearchOptions() 'forcegroupby' => true, 'usehaving' => true, 'datatype' => 'count', + 'use_join_subquery' => true, 'massiveaction' => false, 'joinparams' => [ 'beforejoin' => [ @@ -3570,6 +3571,7 @@ public function rawSearchOptions() 'forcegroupby' => true, 'usehaving' => true, 'datatype' => 'count', + 'use_join_subquery' => true, 'massiveaction' => false, 'joinparams' => [ 'jointype' => 'child', @@ -3585,6 +3587,7 @@ public function rawSearchOptions() 'forcegroupby' => true, 'usehaving' => true, 'datatype' => 'count', + 'use_join_subquery' => true, 'massiveaction' => false, 'joinparams' => [ 'beforejoin' => [ From 640badec5e79a960931d0c4b3571606729852672 Mon Sep 17 00:00:00 2001 From: Rom1-B Date: Fri, 8 Aug 2025 15:19:34 +0200 Subject: [PATCH 3/4] meta search --- src/Glpi/Search/Provider/SQLProvider.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Glpi/Search/Provider/SQLProvider.php b/src/Glpi/Search/Provider/SQLProvider.php index 7f76df3b27c..7617d821994 100644 --- a/src/Glpi/Search/Provider/SQLProvider.php +++ b/src/Glpi/Search/Provider/SQLProvider.php @@ -2877,7 +2877,8 @@ public static function getLeftJoinCriteria( $interlinkfield, (bool) $meta, $meta_type, - $interjoinparams + $interjoinparams, + $use_join_subquery )); // No direct link with the previous joins @@ -5010,7 +5011,8 @@ public static function constructAdditionalSqlForMetacriteria( true, $m_itemtype, $sopt["joinparams"], - $sopt["field"] + $sopt["field"], + $sopt['use_join_subquery'] ?? false ); } } From 46d11bc44ba6d60558e60e0ed647b397f928ab96 Mon Sep 17 00:00:00 2001 From: Rom1-B Date: Fri, 8 Aug 2025 16:26:03 +0200 Subject: [PATCH 4/4] meta search --- src/Glpi/Search/Provider/SQLProvider.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Glpi/Search/Provider/SQLProvider.php b/src/Glpi/Search/Provider/SQLProvider.php index 7617d821994..6f6470e764f 100644 --- a/src/Glpi/Search/Provider/SQLProvider.php +++ b/src/Glpi/Search/Provider/SQLProvider.php @@ -2877,8 +2877,7 @@ public static function getLeftJoinCriteria( $interlinkfield, (bool) $meta, $meta_type, - $interjoinparams, - $use_join_subquery + $interjoinparams )); // No direct link with the previous joins