From a327b5fb5ec9209ab33f90a48b2b49a3e20aee42 Mon Sep 17 00:00:00 2001 From: Eric Date: Wed, 7 Nov 2018 08:50:45 +0100 Subject: [PATCH] Allow an alternative SQL generation from OQL (same as 2.3.0) --- core/config.class.inc.php | 8 +++ core/dbobjectsearch.class.php | 106 +++++++++++++++++++++++----------- 2 files changed, 81 insertions(+), 33 deletions(-) diff --git a/core/config.class.inc.php b/core/config.class.inc.php index 8649de9c7..2d0638e9a 100644 --- a/core/config.class.inc.php +++ b/core/config.class.inc.php @@ -1126,6 +1126,14 @@ class Config 'source_of_value' => '', 'show_in_conf_sample' => true, ), + 'optimize_requests_for_join_count' => array( + 'type' => 'bool', + 'description' => 'Optimize request joins to minimize the count (default is true, try to set it to false in case of performance issues)', + 'default' => true, + 'value' => true, + 'source_of_value' => '', + 'show_in_conf_sample' => true, + ), 'high_cardinality_classes' => array( 'type' => 'array', 'description' => 'List of classes with high cardinality (Force manual submit of search)', diff --git a/core/dbobjectsearch.class.php b/core/dbobjectsearch.class.php index 4ae238ba6..d6e650bdc 100644 --- a/core/dbobjectsearch.class.php +++ b/core/dbobjectsearch.class.php @@ -1937,46 +1937,86 @@ class DBObjectSearch extends DBSearch } } - // First query built from the root, adding all tables including the leaf - // Before N.1065 we were joining from the leaf first, but this wasn't a good choice : - // most of the time (obsolescence, friendlyname, ...) we want to get a root attribute ! - // - $oSelectBase = null; - $aClassHierarchy = MetaModel::EnumParentClasses($sClass, ENUM_PARENT_CLASSES_ALL, true); - $bIsClassStandaloneClass = (count($aClassHierarchy) == 1); - foreach($aClassHierarchy as $sSomeClass) + $bRootFirst = MetaModel::GetConfig()->Get('optimize_requests_for_join_count'); + if ($bRootFirst) { - if (!MetaModel::HasTable($sSomeClass)) + // First query built from the root, adding all tables including the leaf + // Before N.1065 we were joining from the leaf first, but this wasn't a good choice : + // most of the time (obsolescence, friendlyname, ...) we want to get a root attribute ! + // + $oSelectBase = null; + $aClassHierarchy = MetaModel::EnumParentClasses($sClass, ENUM_PARENT_CLASSES_ALL, true); + $bIsClassStandaloneClass = (count($aClassHierarchy) == 1); + foreach($aClassHierarchy as $sSomeClass) { - continue; - } - - self::DbgTrace("Adding join from root to leaf: $sSomeClass... let's call MakeSQLObjectQuerySingleTable()"); - $oSelectParentTable = $this->MakeSQLObjectQuerySingleTable($oBuild, $aAttToLoad, $sSomeClass, $aExtKeys, $aValues); - if (is_null($oSelectBase)) - { - $oSelectBase = $oSelectParentTable; - if (!$bIsClassStandaloneClass && (MetaModel::IsRootClass($sSomeClass))) + if (!MetaModel::HasTable($sSomeClass)) { - // As we're linking to root class first, we're adding a where clause on the finalClass attribute : - // COALESCE($sRootClassFinalClass IN ('$sExpectedClasses'), 1) - // If we don't, the child classes can be removed in the query optimisation phase, including the leaf that was queried - // So we still need to filter records to only those corresponding to the child classes ! - // The coalesce is mandatory if we have a polymorphic query (left join) - $oClassListExpr = ListExpression::FromScalars(MetaModel::EnumChildClasses($sClass, ENUM_CHILD_CLASSES_ALL)); - $sFinalClassSqlColumnName = MetaModel::DBGetClassField($sSomeClass); - $oClassExpr = new FieldExpression($sFinalClassSqlColumnName, $oSelectBase->GetTableAlias()); - $oInExpression = new BinaryExpression($oClassExpr, 'IN', $oClassListExpr); - $oTrueExpression = new TrueExpression(); - $aCoalesceAttr = array($oInExpression, $oTrueExpression); - $oFinalClassRestriction = new FunctionExpression("COALESCE", $aCoalesceAttr); - - $oBuild->m_oQBExpressions->AddCondition($oFinalClassRestriction); + continue; } + + self::DbgTrace("Adding join from root to leaf: $sSomeClass... let's call MakeSQLObjectQuerySingleTable()"); + $oSelectParentTable = $this->MakeSQLObjectQuerySingleTable($oBuild, $aAttToLoad, $sSomeClass, $aExtKeys, $aValues); + if (is_null($oSelectBase)) + { + $oSelectBase = $oSelectParentTable; + if (!$bIsClassStandaloneClass && (MetaModel::IsRootClass($sSomeClass))) + { + // As we're linking to root class first, we're adding a where clause on the finalClass attribute : + // COALESCE($sRootClassFinalClass IN ('$sExpectedClasses'), 1) + // If we don't, the child classes can be removed in the query optimisation phase, including the leaf that was queried + // So we still need to filter records to only those corresponding to the child classes ! + // The coalesce is mandatory if we have a polymorphic query (left join) + $oClassListExpr = ListExpression::FromScalars(MetaModel::EnumChildClasses($sClass, ENUM_CHILD_CLASSES_ALL)); + $sFinalClassSqlColumnName = MetaModel::DBGetClassField($sSomeClass); + $oClassExpr = new FieldExpression($sFinalClassSqlColumnName, $oSelectBase->GetTableAlias()); + $oInExpression = new BinaryExpression($oClassExpr, 'IN', $oClassListExpr); + $oTrueExpression = new TrueExpression(); + $aCoalesceAttr = array($oInExpression, $oTrueExpression); + $oFinalClassRestriction = new FunctionExpression("COALESCE", $aCoalesceAttr); + + $oBuild->m_oQBExpressions->AddCondition($oFinalClassRestriction); + } + } + else + { + $oSelectBase->AddInnerJoin($oSelectParentTable, $sKeyField, MetaModel::DBGetKey($sSomeClass)); + } + } + } + else + { + // First query built upon on the leaf (ie current) class + // + self::DbgTrace("Main (=leaf) class, call MakeSQLObjectQuerySingleTable()"); + if (MetaModel::HasTable($sClass)) + { + $oSelectBase = $this->MakeSQLObjectQuerySingleTable($oBuild, $aAttToLoad, $sClass, $aExtKeys, $aValues); } else { - $oSelectBase->AddInnerJoin($oSelectParentTable, $sKeyField, MetaModel::DBGetKey($sSomeClass)); + $oSelectBase = null; + + // As the join will not filter on the expected classes, we have to specify it explicitely + $sExpectedClasses = implode("', '", MetaModel::EnumChildClasses($sClass, ENUM_CHILD_CLASSES_ALL)); + $oFinalClassRestriction = Expression::FromOQL("`$sClassAlias`.finalclass IN ('$sExpectedClasses')"); + $oBuild->m_oQBExpressions->AddCondition($oFinalClassRestriction); + } + + // Then we join the queries of the eventual parent classes (compound model) + foreach(MetaModel::EnumParentClasses($sClass) as $sParentClass) + { + if (!MetaModel::HasTable($sParentClass)) continue; + + self::DbgTrace("Parent class: $sParentClass... let's call MakeSQLObjectQuerySingleTable()"); + $oSelectParentTable = $this->MakeSQLObjectQuerySingleTable($oBuild, $aAttToLoad, $sParentClass, $aExtKeys, $aValues); + if (is_null($oSelectBase)) + { + $oSelectBase = $oSelectParentTable; + } + else + { + $oSelectBase->AddInnerJoin($oSelectParentTable, $sKeyField, MetaModel::DBGetKey($sParentClass)); + } } }