Index: kernel/db/dblist.php =================================================================== --- kernel/db/dblist.php (revision 14184) +++ kernel/db/dblist.php (working copy) @@ -542,6 +542,11 @@ $order = $this->GetOrderClause(); $group = $this->GetGroupClause(); + if ($for_counting) { + $optimizer = new LeftJoinOptimizer($q, $where . '|' . $having . '|' . $order . '|' . $group); + $q = $optimizer->simplify(); + } + if (!empty($where)) $q .= ' WHERE ' . $where; if (!empty($group)) $q .= ' GROUP BY ' . $group; if (!empty($having)) $q .= ' HAVING ' . $having; @@ -1105,4 +1110,172 @@ return true; } +} + +class LeftJoinOptimizer { + + /** + * Input sql for optimization + * + * @var string + */ + var $sql = ''; + + /** + * All sql parts, where LEFT JOINed table aliases could be used + * + * @var string + */ + var $usageString = ''; + + /** + * List of discovered LEFT JOINs + * + * @var Array + */ + var $joins = Array (); + + /** + * LEFT JOIN relations + * + * @var Array + */ + var $joinRelations = Array (); + + /** + * LEFT JOIN table aliases scheduled for removal + * + * @var Array + */ + var $aliasesToRemove = Array (); + + function LeftJoinOptimizer($sql, $usage_string) + { + $this->sql = $sql; + $this->usageString = $usage_string; + + $this->parseJoins(); + } + + /** + * Tries to remove unused LEFT JOINs + * + * @return string + */ + function simplify() + { + if ( !$this->joins ) { + // no LEFT JOIN used, return unchanged sql + return $this->sql; + } + + $this->updateRelations(); + $this->removeAliases(); + + return $this->sql; + } + + /** + * Discovers LEFT JOINs based on given sql + * + */ + function parseJoins() + { + if ( !preg_match_all('/LEFT\s+JOIN\s+(.*?|.*?\s+AS\s+.*?|.*?\s+.*?)\s+ON\s+(.*?\n|.*?$)/i', $this->sql, $regs) ) { + $this->joins = Array (); + } + + // get all LEFT JOIN clause info from sql (without filters) + foreach ($regs[1] as $index => $match) { + $match_parts = preg_split('/\s+AS\s+|\s+/i', $match, 2); + $table_alias = count($match_parts) == 1 ? $match : $match_parts[1]; + + $this->joins[$table_alias] = Array ( + 'table' => $match_parts[0], + 'join_clause' => $regs[0][$index], + ); + } + } + + /** + * Detects relations between LEFT JOINs + * + */ + function updateRelations() + { + foreach ($this->joins as $table_alias => $left_join_info) { + $escaped_alias = preg_quote($table_alias, '/'); + + foreach ($this->joins as $sub_table_alias => $sub_left_join_info) { + if ($table_alias == $sub_table_alias) { + continue; + } + + if ( $this->matchAlias($escaped_alias, $sub_left_join_info['join_clause']) ) { + $this->joinRelations[] = $sub_table_alias . ':' . $table_alias; + } + } + } + } + + /** + * Removes scheduled LEFT JOINs, but only if they are not protected + * + */ + function removeAliases() + { + $this->prepareAliasesRemoval(); + + foreach ($this->aliasesToRemove as $to_remove_alias) { + if ( !$this->aliasProtected($to_remove_alias) ) { + $this->sql = str_replace($this->joins[$to_remove_alias]['join_clause'], '', $this->sql); + } + } + } + + /** + * Schedules unused LEFT JOINs to for removal + * + */ + function prepareAliasesRemoval() + { + foreach ($this->joins as $table_alias => $left_join_info) { + $escaped_alias = preg_quote($table_alias, '/'); + + if ( !$this->matchAlias($escaped_alias, $this->usageString) ) { + $this->aliasesToRemove[] = $table_alias; + } + } + } + + /** + * Checks if someone wants to remove LEFT JOIN, but it's used by some other LEFT JOIN, that stays + * + * @param string $table_alias + * @return bool + */ + function aliasProtected($table_alias) + { + foreach ($this->joinRelations as $relation) { + list ($main_alias, $used_alias) = explode(':', $relation); + + if ( ($used_alias == $table_alias) && !in_array($main_alias, $this->aliasesToRemove) ) { + return true; + } + } + + return false; + } + + /** + * Matches given escaped alias to a string + * + * @param string $escaped_alias + * @param string $string + * @return bool + */ + function matchAlias($escaped_alias, $string) + { + return preg_match('/(`' . $escaped_alias . '`|' . $escaped_alias . ')\./', $string); + } } \ No newline at end of file Index: units/categories/categories_event_handler.php =================================================================== --- units/categories/categories_event_handler.php (revision 14184) +++ units/categories/categories_event_handler.php (working copy) @@ -304,16 +304,16 @@ } } else { - $object->addFilter('parent_filter', 'ParentId = '.$parent_cat_id); + $object->addFilter('parent_filter', '%1$s.ParentId = '.$parent_cat_id); } } - $object->addFilter('perm_filter', 'PermId = 1'); // check for CATEGORY.VIEW permission + $object->addFilter('perm_filter', TABLE_PREFIX . 'PermCache.PermId = 1'); // check for CATEGORY.VIEW permission if ($this->Application->RecallVar('user_id') != USER_ROOT) { // apply permission filters to all users except "root" $groups = explode(',',$this->Application->RecallVar('UserGroups')); foreach ($groups as $group) { - $view_filters[] = 'FIND_IN_SET('.$group.', acl)'; + $view_filters[] = 'FIND_IN_SET('.$group.', ' . TABLE_PREFIX . 'PermCache.ACL)'; } $view_filter = implode(' OR ', $view_filters); $object->addFilter('perm_filter2', $view_filter); Index: units/modules/modules_event_handler.php =================================================================== --- units/modules/modules_event_handler.php (revision 14184) +++ units/modules/modules_event_handler.php (working copy) @@ -37,7 +37,7 @@ { $object =& $event->getObject(); if ($event->Special) { - $object->addFilter('current_module', 'Name = '.$event->Special); + $object->addFilter('current_module', '%1$s.Name = '.$event->Special); } $object->addFilter('not_core', '%1$s.Name <> "Core"');