Merge branch 'support/2.7' into develop

This commit is contained in:
Eric
2021-02-08 10:42:19 +01:00
11 changed files with 198 additions and 60 deletions

View File

@@ -2602,9 +2602,18 @@ class DBObjectSearch extends DBSearch
return $oExpression;
}
/**
* @param array $aAttCodes array of attCodes to search into
* @param string $sNeedle one word to be searched
*
* @throws \CoreException
*/
public function AddCondition_FullTextOnAttributes(array $aAttCodes, $sNeedle)
{
}
public function ListParameters()
{
return $this->GetCriteria()->ListParameters();
}
}

View File

@@ -1919,6 +1919,39 @@ class FieldExpression extends UnaryExpression
// Has been resolved into an SQL expression
class FieldExpressionResolved extends FieldExpression
{
protected $m_aAdditionalExpressions;
public function __construct($mExpression, $sParent = '')
{
$this->m_aAdditionalExpressions = array();
if (is_array($mExpression))
{
foreach ($mExpression as $sSuffix => $sExpression)
{
if ($sSuffix == '')
{
$sName = $sExpression;
}
$this->m_aAdditionalExpressions[$sSuffix] = new FieldExpressionResolved($sExpression, $sParent);
}
}
else
{
$sName = $mExpression;
}
parent::__construct($sName, $sParent);
}
/**
* @return array of additional expressions for muti-column attributes
* @since 2.7.4
*/
public function AdditionalExpressions()
{
return $this->m_aAdditionalExpressions;
}
public function GetUnresolvedFields($sAlias, &$aUnresolved)
{
}

View File

@@ -331,4 +331,12 @@ class OQLJoin
return $this->sRightField;
}
/**
* @return string
*/
public function GetLeftField()
{
return $this->sLeftField;
}
}

View File

@@ -50,8 +50,15 @@ class OQLClassTreeOptimizer
{
if ($oJoin->IsOutbound())
{
// The join is not used, remove from tree
$oCurrentClassNode->RemoveJoin($sLeftKey, $index);
// If joined class in not the same class than the external key target class
// then the join cannot be removed because it is used to filter the request
$sJoinedClass = $oJoin->GetOOQLClassNode()->GetNodeClass();
$sExtKeyAttCode = $oJoin->GetLeftField();
$oExtKeyAttDef = MetaModel::GetAttributeDef($oCurrentClassNode->GetNodeClass(), $sExtKeyAttCode);
if ($sJoinedClass == $oExtKeyAttDef->GetTargetClass()) {
// The join is not used, remove from tree
$oCurrentClassNode->RemoveJoin($sLeftKey, $index);
}
}
else
{

View File

@@ -178,6 +178,14 @@ class QueryBuilderExpressions
foreach ($this->m_aSelectExpr as $sColAlias => $oExpr)
{
$this->m_aSelectExpr[$sColAlias] = $oExpr->Translate($aTranslationData, $bMatchAll, $bMarkFieldsAsResolved);
if ($this->m_aSelectExpr[$sColAlias] instanceof FieldExpressionResolved)
{
// Split the field with the relevant alias
foreach ($this->m_aSelectExpr[$sColAlias]->AdditionalExpressions() as $sSuffix => $oAdditionalExpr)
{
$this->m_aSelectExpr[$sColAlias.$sSuffix] = $oAdditionalExpr->Translate($aTranslationData, $bMatchAll, $bMarkFieldsAsResolved);
}
}
}
if ($this->m_aGroupByExpr)
{

View File

@@ -239,24 +239,16 @@ class SQLObjectQueryBuilder
continue;
}
$oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
foreach ($oAttDef->GetSQLExpressions() as $sColId => $sSQLExpr)
$oFieldSQLExp = new FieldExpressionResolved($oAttDef->GetSQLExpressions(), $sClassAlias);
/**
* @var string $sPluginClass
* @var iQueryModifier $oQueryModifier
*/
foreach (MetaModel::EnumPlugins('iQueryModifier') as $sPluginClass => $oQueryModifier)
{
if (!empty($sColId))
{
// Multi column attributes
$oBuild->m_oQBExpressions->AddSelect($sSelectedClassAlias.$sAttCode.$sColId, new FieldExpression($sAttCode.$sColId, $sClassAlias));
}
$oFieldSQLExp = new FieldExpressionResolved($sSQLExpr, $sClassAlias);
/**
* @var string $sPluginClass
* @var iQueryModifier $oQueryModifier
*/
foreach (MetaModel::EnumPlugins('iQueryModifier') as $sPluginClass => $oQueryModifier)
{
$oFieldSQLExp = $oQueryModifier->GetFieldExpression($oBuild, $sClass, $sAttCode, $sColId, $oFieldSQLExp, $oBaseSQLQuery);
}
$aTranslation[$sClassAlias][$sAttCode.$sColId] = $oFieldSQLExp;
$oFieldSQLExp = $oQueryModifier->GetFieldExpression($oBuild, $sClass, $sAttCode, '', $oFieldSQLExp, $oBaseSQLQuery);
}
$aTranslation[$sClassAlias][$sAttCode] = $oFieldSQLExp;
}
// Translate the selected columns

View File

@@ -813,7 +813,7 @@ Dict::Add('NL NL', 'Dutch', 'Nederlands', array(
'UI:Delete:WillBeDeletedAutomatically' => 'Zal automatisch verwijderd worden',
'UI:Delete:MustBeDeletedManually' => 'Moet handmatig verwijderd worden',
'UI:Delete:CannotUpdateBecause_Issue' => 'Zou automatisch moeten geüpdatet worden, maar: %1$s',
'UI:Delete:WillAutomaticallyUpdate_Fields' => 'Zal automatisch aangeapst worden (reset: %1$s)',
'UI:Delete:WillAutomaticallyUpdate_Fields' => 'Zal automatisch aangepast worden (reset: %1$s)',
'UI:Delete:Count_Objects/LinksReferencing_Object' => '%1$d objecten/links verwijzen naar %2$s',
'UI:Delete:Count_Objects/LinksReferencingTheObjects' => '%1$d objecten/links verwijzen naar sommige objecten die verwijderd worden',
'UI:Delete:ReferencesMustBeDeletedToEnsureIntegrity' => 'Elke verdere verwijzing moet verwijderd worden om de integriteit van de database te verzekeren',

View File

@@ -12,7 +12,7 @@
{% if bFailedLogin %}
<p class="hilite">{{ sMessage }}</p>
{% else %}
<p>{{ sMessage }}</p>
<p>{{ sMessage|raw }}</p>
{% endif %}
</div>
{% endblock login_title %}

View File

@@ -92,7 +92,31 @@ class ItopTestCase extends TestCase
{
$sId = str_replace('"', '', $this->getName());
$sId = str_replace(' ', '_', $sId);
return $sId;
}
public function InvokeNonPublicStaticMethod($sObjectClass, $sMethodName, $aArgs)
{
return $this->InvokeNonPublicMethod($sObjectClass, $sMethodName, null, $aArgs);
}
/**
* @param string $sObjectClass for example DBObject::class
* @param string $sMethodName
* @param object $oObject
* @param array $aArgs
*
* @return mixed method result
*
* @throws \ReflectionException
*/
public function InvokeNonPublicMethod($sObjectClass, $sMethodName, $oObject, $aArgs)
{
$class = new \ReflectionClass($sObjectClass);
$method = $class->getMethod($sMethodName);
$method->setAccessible(true);
return $method->invokeArgs($oObject, $aArgs);
}
}

View File

@@ -33,7 +33,9 @@ class DBSearchUpdateRealiasingMapTest extends ItopDataTestCase
*/
public function testUpdateRealiasingMap($aRealiasingMap, $aAliasTranslation, $aExpectedRealiasingMap)
{
$this->UpdateRealiasingMap($aRealiasingMap, $aAliasTranslation);
$oObject = new DBObjectSearch('Organization');
$aArgs = [&$aRealiasingMap, $aAliasTranslation];
$this->InvokeNonPublicMethod(DBObjectSearch::class, 'UpdateRealiasingMap', $oObject, $aArgs);
$this->assertEquals($aExpectedRealiasingMap, $aRealiasingMap);
}
@@ -77,12 +79,4 @@ class DBSearchUpdateRealiasingMapTest extends ItopDataTestCase
],
];
}
private function UpdateRealiasingMap(&$aRealiasingMap, $aAliasTranslation)
{
$class = new \ReflectionClass(DBObjectSearch::class);
$method = $class->getMethod('UpdateRealiasingMap');
$method->setAccessible(true);
$method->invokeArgs(new DBObjectSearch('Organization'), [&$aRealiasingMap, $aAliasTranslation]);
}
}

View File

@@ -15,7 +15,10 @@ use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
use DBObjectSearch;
use DBSearch;
use Exception;
use MetaModel;
use OqlInterpreter;
use QueryBuilderContext;
use SQLObjectQueryBuilder;
use utils;
/**
@@ -51,6 +54,7 @@ class OQLTest extends ItopDataTestCase
* @group itopConfigMgmt
* @group itopRequestMgmt
* @dataProvider NestedQueryProvider
* @depends testOQLSetup
*
* @param $sQuery
*
@@ -410,44 +414,103 @@ class OQLTest extends ItopDataTestCase
);
}
/**
* @dataProvider bug3618Provider
* @doesNotPerformAssertions
*/
public function testBug3618($sOQL, $sExplanation)
{
$this->markTestSkipped();
return;
if (is_string($sExplanation)) {
$this->debug($sExplanation);
}
$oSearch = DBSearch::FromOQL($sOQL);
$oSet = new \CMDBObjectSet($oSearch);
$oSet->CountWithLimit(1);
/**
* @dataProvider GetOQLClassTreeProvider
* @param $sOQL
* @param $sExpectedOQL
*/
public function testGetOQLClassTree($sOQL, $sExpectedOQL)
{
$oFilter = DBSearch::FromOQL($sOQL);
$aCountAttToLoad = array();
$sMainClass = null;
foreach ($oFilter->GetSelectedClasses() as $sClassAlias => $sClass)
{
$aCountAttToLoad[$sClassAlias] = array();
if (empty($sMainClass))
{
$sMainClass = $sClass;
}
}
$aModifierProperties = MetaModel::MakeModifierProperties($oFilter);
$oSQLObjectQueryBuilder = new SQLObjectQueryBuilder($oFilter);
$oBuild = new QueryBuilderContext($oFilter, $aModifierProperties, null, null, null, $aCountAttToLoad);
$sResultOQL = $oSQLObjectQueryBuilder->DebugOQLClassTree($oBuild);
static::assertEquals($sExpectedOQL, $sResultOQL);
}
public function bug3618Provider()
public function GetOQLClassTreeProvider()
{
return [
'ok' => [
'sOql' => "SELECT UserRequest WHERE private_log LIKE '%FOO : %'
UNION
SELECT Ticket WHERE private_log LIKE '%BAR : %'",
'sExplanation' => null,
'Bug 3660 1' => [
"SELECT UserRequest AS U JOIN lnkContactToTicket AS l ON l.ticket_id=U.id JOIN Team AS T ON l.contact_id=T.id",
"SELECT `U` FROM `UserRequest` AS `U`
INNER JOIN `lnkContactToTicket` AS `l`
ON `U`.`id` = `l`.`ticket_id`
INNER JOIN `Team` AS `T`
ON `l`.`contact_id` = `T`.`id`",
],
'KO: different number of columns' => [
'sOql' => "SELECT UserRequest WHERE private_log LIKE '%FOO : %'
UNION
SELECT Ticket ",
'sExplanation' => 'The UNION is composed by two SELECT stmt, the 1st one has two columns the second one has only one column: BOOM!',
'Bug 3660 2' => [
"SELECT UserRequest AS U JOIN lnkContactToTicket AS l ON l.ticket_id=U.id JOIN Contact AS C ON l.contact_id=C.id",
"SELECT `U` FROM `UserRequest` AS `U`
INNER JOIN `lnkContactToTicket` AS `l`
ON `U`.`id` = `l`.`ticket_id`",
],
];
}
/**
* @dataProvider MakeSelectQueryForCountProvider
*
* @param $sOQL
* @param $sExpectedSQL
*
* @throws \CoreException
* @throws \MissingQueryArgument
* @throws \OQLException
*/
public function testMakeSelectQueryForCount($sOQL, $sExpectedSQL)
{
$oFilter = DBSearch::FromOQL($sOQL);
// Avoid adding all the fields for counts or "group by" requests
$aCountAttToLoad = array();
$sMainClass = null;
foreach ($oFilter->GetSelectedClasses() as $sClassAlias => $sClass) {
$aCountAttToLoad[$sClassAlias] = array();
if (empty($sMainClass)) {
$sMainClass = $sClass;
}
}
$sSQL = $oFilter->MakeSelectQuery([], [], $aCountAttToLoad, null, 0, 0, true);
static::assertEquals($sExpectedSQL, $sSQL);
}
public function MakeSelectQueryForCountProvider()
{
return [
'Bug 3618' => [
"SELECT UserRequest WHERE private_log LIKE '%Auteur : %' UNION SELECT UserRequest",
"SELECT COUNT(*) AS COUNT FROM (SELECT
1
FROM (
SELECT
DISTINCT `UserRequest_Ticket`.`id` AS `UserRequestid`
FROM
`ticket` AS `UserRequest_Ticket`
WHERE ((`UserRequest_Ticket`.`private_log` LIKE '%Auteur : %') AND COALESCE((`UserRequest_Ticket`.`finalclass` IN ('UserRequest')), 1))
UNION
SELECT
DISTINCT `UserRequest`.`id` AS `UserRequestid`
FROM
`ticket_request` AS `UserRequest`
WHERE 1
) as __selects__
) AS _union_alderaan_",
],
];
}
}