mirror of
https://github.com/Combodo/iTop.git
synced 2026-02-13 07:24:13 +01:00
N°3171 - Friendly name and obsolescence flag not refreshed (#151)
- Compute any type of expression on server side - Recompute friendly name and obsolescence flag on server side (DBOBject) - Bonus : compute dependency for external keys
This commit is contained in:
@@ -6653,6 +6653,23 @@ class AttributeExternalKey extends AttributeDBFieldVoid
|
||||
return (int)$proposedValue;
|
||||
}
|
||||
|
||||
public function GetPrerequisiteAttributes($sClass = null)
|
||||
{
|
||||
$aAttributes = parent::GetPrerequisiteAttributes($sClass);
|
||||
$oExpression = DBSearch::FromOQL($this->GetValuesDef()->GetFilterExpression())->GetCriteria();
|
||||
foreach ($oExpression->GetParameters('this') as $sAttCode)
|
||||
{
|
||||
// Skip the id as it cannot change anyway
|
||||
if ($sAttCode =='id') continue;
|
||||
|
||||
if (!in_array($sAttCode, $aAttributes))
|
||||
{
|
||||
$aAttributes[] = $sAttCode;
|
||||
}
|
||||
}
|
||||
return $aAttributes;
|
||||
}
|
||||
|
||||
public function GetMaximumComboLength()
|
||||
{
|
||||
return $this->GetOptional('max_combo_length', MetaModel::GetConfig()->Get('max_combo_length'));
|
||||
@@ -11561,7 +11578,17 @@ class AttributeFriendlyName extends AttributeDefinition
|
||||
|
||||
public function GetPrerequisiteAttributes($sClass = null)
|
||||
{
|
||||
return $this->GetOptional("depends_on", array());
|
||||
// Code duplicated with AttributeObsolescenceFlag
|
||||
$aAttributes = $this->GetOptional("depends_on", array());
|
||||
$oExpression = $this->GetOQLExpression();
|
||||
foreach ($oExpression->ListRequiredFields() as $sClass => $sAttCode)
|
||||
{
|
||||
if (!in_array($sAttCode, $aAttributes))
|
||||
{
|
||||
$aAttributes[] = $sAttCode;
|
||||
}
|
||||
}
|
||||
return $aAttributes;
|
||||
}
|
||||
|
||||
public static function IsScalar()
|
||||
@@ -12793,7 +12820,17 @@ class AttributeObsolescenceFlag extends AttributeBoolean
|
||||
|
||||
public function GetPrerequisiteAttributes($sClass = null)
|
||||
{
|
||||
return $this->GetOptional("depends_on", array());
|
||||
// Code duplicated with AttributeFriendlyName
|
||||
$aAttributes = $this->GetOptional("depends_on", array());
|
||||
$oExpression = $this->GetOQLExpression();
|
||||
foreach ($oExpression->ListRequiredFields() as $sClass => $sAttCode)
|
||||
{
|
||||
if (!in_array($sAttCode, $aAttributes))
|
||||
{
|
||||
$aAttributes[] = $sAttCode;
|
||||
}
|
||||
}
|
||||
return $aAttributes;
|
||||
}
|
||||
|
||||
public function IsDirectField()
|
||||
|
||||
@@ -569,43 +569,41 @@ abstract class DBObject implements iDisplay
|
||||
$this->Reload();
|
||||
}
|
||||
|
||||
if ($oAttDef->IsExternalKey())
|
||||
if ($oAttDef->IsExternalKey() && is_object($value))
|
||||
{
|
||||
if (is_object($value))
|
||||
// Setting an external key with a whole object (instead of just an ID)
|
||||
// let's initialize also the external fields that depend on it
|
||||
// (useful when building objects in memory and not from a query)
|
||||
/** @var \AttributeExternalKey $oAttDef */
|
||||
if ((get_class($value) != $oAttDef->GetTargetClass()) && (!is_subclass_of($value, $oAttDef->GetTargetClass())))
|
||||
{
|
||||
// Setting an external key with a whole object (instead of just an ID)
|
||||
// let's initialize also the external fields that depend on it
|
||||
// (useful when building objects in memory and not from a query)
|
||||
/** @var \AttributeExternalKey $oAttDef */
|
||||
if ( (get_class($value) != $oAttDef->GetTargetClass()) && (!is_subclass_of($value, $oAttDef->GetTargetClass())))
|
||||
{
|
||||
throw new CoreUnexpectedValue("Trying to set the value of '$sAttCode', to an object of class '".get_class($value)."', whereas it's an ExtKey to '".$oAttDef->GetTargetClass()."'. Ignored");
|
||||
}
|
||||
throw new CoreUnexpectedValue("Trying to set the value of '$sAttCode', to an object of class '".get_class($value)."', whereas it's an ExtKey to '".$oAttDef->GetTargetClass()."'. Ignored");
|
||||
}
|
||||
|
||||
foreach(MetaModel::ListAttributeDefs(get_class($this)) as $sCode => $oDef)
|
||||
foreach (MetaModel::ListAttributeDefs(get_class($this)) as $sCode => $oDef)
|
||||
{
|
||||
/** @var \AttributeExternalField $oDef */
|
||||
if ($oDef->IsExternalField() && ($oDef->GetKeyAttCode() == $sAttCode))
|
||||
{
|
||||
/** @var \AttributeExternalField $oDef */
|
||||
if ($oDef->IsExternalField() && ($oDef->GetKeyAttCode() == $sAttCode))
|
||||
{
|
||||
/** @var \DBObject $value */
|
||||
$this->m_aCurrValues[$sCode] = $value->Get($oDef->GetExtAttCode());
|
||||
$this->m_aLoadedAtt[$sCode] = true;
|
||||
}
|
||||
/** @var \DBObject $value */
|
||||
$this->m_aCurrValues[$sCode] = $value->Get($oDef->GetExtAttCode());
|
||||
$this->m_aLoadedAtt[$sCode] = true;
|
||||
}
|
||||
elseif (in_array($sAttCode, $oDef->GetPrerequisiteAttributes(get_class($this))))
|
||||
{
|
||||
$this->m_aCurrValues[$sCode] = $this->GetDefaultValue($sCode);
|
||||
unset($this->m_aLoadedAtt[$sCode]);
|
||||
}
|
||||
}
|
||||
else if ($this->m_aCurrValues[$sAttCode] != $value)
|
||||
}
|
||||
else if ($this->m_aCurrValues[$sAttCode] !== $value)
|
||||
{
|
||||
// Invalidate dependent fields so that they get reloaded in case they are needed (See Get())
|
||||
//
|
||||
foreach (MetaModel::GetDependentAttributes(get_class($this), $sAttCode) as $sCode)
|
||||
{
|
||||
// Setting an external key, but no any other information is available...
|
||||
// Invalidate the corresponding fields so that they get reloaded in case they are needed (See Get())
|
||||
foreach(MetaModel::ListAttributeDefs(get_class($this)) as $sCode => $oDef)
|
||||
{
|
||||
/** @var \AttributeExternalKey $oDef */
|
||||
if ($oDef->IsExternalField() && ($oDef->GetKeyAttCode() == $sAttCode))
|
||||
{
|
||||
$this->m_aCurrValues[$sCode] = $this->GetDefaultValue($sCode);
|
||||
unset($this->m_aLoadedAtt[$sCode]);
|
||||
}
|
||||
}
|
||||
$this->m_aCurrValues[$sCode] = $this->GetDefaultValue($sCode);
|
||||
unset($this->m_aLoadedAtt[$sCode]);
|
||||
}
|
||||
}
|
||||
if ($oAttDef->IsLinkSet() && ($value != null))
|
||||
@@ -787,20 +785,20 @@ abstract class DBObject implements iDisplay
|
||||
{
|
||||
// Standard case... we have the information directly
|
||||
}
|
||||
elseif ($this->m_bIsInDB && !$this->m_bDirty)
|
||||
elseif ($this->m_bIsInDB && !$this->m_bFullyLoaded && !$this->m_bDirty)
|
||||
{
|
||||
// Lazy load (polymorphism): complete by reloading the entire object
|
||||
// #@# non-scalar attributes.... handle that differently?
|
||||
$oKPI = new ExecutionKPI();
|
||||
$this->Reload();
|
||||
$oKPI->ComputeStats('Reload', get_class($this).'/'.$sAttCode);
|
||||
}
|
||||
elseif ($sAttCode == 'friendlyname')
|
||||
elseif ($oAttDef->IsBasedOnOQLExpression())
|
||||
{
|
||||
// The friendly name is not computed and the object is dirty
|
||||
// Todo: implement the computation of the friendly name based on sprintf()
|
||||
//
|
||||
$this->m_aCurrValues[$sAttCode] = '';
|
||||
// Recompute -which is likely to call Get()
|
||||
//
|
||||
/** @var AttributeFriendlyName|\AttributeObsolescenceFlag $oAttDef */
|
||||
$this->m_aCurrValues[$sAttCode] = $this->EvaluateExpression($oAttDef->GetOQLExpression());
|
||||
$this->m_aLoadedAtt[$sAttCode] = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -5358,5 +5356,33 @@ abstract class DBObject implements iDisplay
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public function EvaluateExpression(Expression $oExpression)
|
||||
{
|
||||
$aFields = $oExpression->ListRequiredFields();
|
||||
$aArgs = array();
|
||||
foreach ($aFields as $sFieldDesc)
|
||||
{
|
||||
$aFieldParts = explode('.', $sFieldDesc);
|
||||
if (count($aFieldParts) == 2)
|
||||
{
|
||||
$sClass = $aFieldParts[0];
|
||||
$sAttCode = $aFieldParts[1];
|
||||
}
|
||||
else
|
||||
{
|
||||
$sClass = get_class($this);
|
||||
$sAttCode = $aFieldParts[0];
|
||||
}
|
||||
if (get_class($this) != $sClass) continue;
|
||||
if (!MetaModel::IsValidAttCode(get_class($this), $sAttCode)) continue;
|
||||
|
||||
$oAttDef = MetaModel::GetAttributeDef(get_class($this), $sAttCode);
|
||||
$aSQLValues = $oAttDef->GetSQLValues($this->m_aCurrValues[$sAttCode]);
|
||||
$value = reset($aSQLValues);
|
||||
$aArgs[$sFieldDesc] = $value;
|
||||
}
|
||||
return $oExpression->Evaluate($aArgs);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -944,11 +944,6 @@ abstract class MetaModel
|
||||
{
|
||||
self::_check_subclass($sClass);
|
||||
$oAtt = self::GetAttributeDef($sClass, $sAttCode);
|
||||
// Temporary implementation: later, we might be able to compute
|
||||
// the dependencies, based on the attributes definition
|
||||
// (allowed values and default values)
|
||||
|
||||
// Even non-writable attributes (like ExternalFields) can now have Prerequisites
|
||||
return $oAtt->GetPrerequisiteAttributes();
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,9 @@ class MissingQueryArgument extends CoreException
|
||||
{
|
||||
}
|
||||
|
||||
class NotYetEvaluatedExpression extends CoreException
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @method Check($oModelReflection, array $aAliases, $sSourceQuery)
|
||||
@@ -109,6 +112,47 @@ abstract class Expression
|
||||
*/
|
||||
abstract public function RenderExpression($bForSQL = false, &$aArgs = null, $bRetrofitParams = false);
|
||||
|
||||
/**
|
||||
* Collect parameters, i.e. :parameter
|
||||
*
|
||||
* @param null $sParentFilter
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function GetParameters($sParentFilter = null)
|
||||
{
|
||||
$aParameters = array();
|
||||
$unused = $this->RenderExpression(false, $aParameters, true);
|
||||
|
||||
if (!is_null($sParentFilter)) $sParentFilter .= '->';
|
||||
|
||||
$aRet = array();
|
||||
foreach($aParameters as $sParameter => $unused)
|
||||
{
|
||||
if (is_null($sParentFilter))
|
||||
{
|
||||
$aRet[] = $sParameter;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (substr($sParameter, 0, strlen($sParentFilter)) == $sParentFilter)
|
||||
{
|
||||
$aRet[] = substr($sParameter, strlen($sParentFilter));
|
||||
}
|
||||
}
|
||||
}
|
||||
return $aRet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluate the value of the expression
|
||||
*
|
||||
* @param array $aArgs
|
||||
*
|
||||
* @throws \Exception if terms cannot be evaluated as scalars
|
||||
*/
|
||||
abstract public function Evaluate(array $aArgs);
|
||||
|
||||
/**
|
||||
* Recursively renders the expression as a structure (array) suitable for a JSON export
|
||||
*
|
||||
@@ -319,6 +363,16 @@ class SQLExpression extends Expression
|
||||
return $this->m_sSQL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluate the value of the expression
|
||||
* @param array $aArgs
|
||||
* @throws \Exception if terms cannot be evaluated as scalars
|
||||
*/
|
||||
public function Evaluate(array $aArgs)
|
||||
{
|
||||
throw new Exception('a nested query cannot be evaluated');
|
||||
}
|
||||
|
||||
// recursive rendering
|
||||
public function toJSON(&$aArgs = null, $bRetrofitParams = false)
|
||||
{
|
||||
@@ -466,6 +520,149 @@ class BinaryExpression extends Expression
|
||||
return "($sLeft $sOperator $sRight)";
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluate the value of the expression
|
||||
* @param array $aArgs
|
||||
* @return mixed
|
||||
* @throws \Exception if terms cannot be evaluated as scalars
|
||||
*/
|
||||
public function Evaluate(array $aArgs)
|
||||
{
|
||||
$mLeft = $this->GetLeftExpr()->Evaluate($aArgs);
|
||||
$mRight = $this->GetRightExpr()->Evaluate($aArgs);
|
||||
|
||||
$sOperator = $this->GetOperator();
|
||||
$sType = null;
|
||||
switch($sOperator)
|
||||
{
|
||||
case '+':
|
||||
case '-':
|
||||
case '*':
|
||||
case '/':
|
||||
$sType = 'maths';
|
||||
break;
|
||||
case '=':
|
||||
case '!=':
|
||||
case '<>':
|
||||
$sType = 'comp';
|
||||
break;
|
||||
case '>':
|
||||
case '>=':
|
||||
case '<':
|
||||
case '<=':
|
||||
$sType = 'numcomp';
|
||||
break;
|
||||
case 'OR':
|
||||
case 'AND':
|
||||
$sType = 'logical';
|
||||
break;
|
||||
case 'LIKE':
|
||||
$sType = 'like';
|
||||
break;
|
||||
default:
|
||||
throw new Exception("Operator '$sOperator' not yet supported");
|
||||
}
|
||||
switch ($sType){
|
||||
case 'logical':
|
||||
$bLeft = static::CastToBool($mLeft);
|
||||
$bRight = static::CastToBool($mRight);
|
||||
switch ($sOperator)
|
||||
{
|
||||
case 'OR':
|
||||
$result = (int)($bLeft || $bRight);
|
||||
break;
|
||||
case 'AND':
|
||||
$result = (int)($bLeft && $bRight);
|
||||
break;
|
||||
default:
|
||||
throw new Exception("Logic: unknown operator '$sOperator'");
|
||||
}
|
||||
break;
|
||||
|
||||
case 'maths':
|
||||
$iLeft = (int) $mLeft;
|
||||
$iRight = (int) $mRight;
|
||||
switch ($sOperator)
|
||||
{
|
||||
case '+' : $result = $iLeft + $iRight; break;
|
||||
case '-' : $result = $iLeft - $iRight; break;
|
||||
case '*' : $result = $iLeft * $iRight; break;
|
||||
case '/' : $result = $iLeft / $iRight; break;
|
||||
default:
|
||||
throw new Exception("Logic: unknown operator '$sOperator'");
|
||||
}
|
||||
break;
|
||||
case 'comp':
|
||||
$left = $mLeft;
|
||||
$right = $mRight;
|
||||
switch ($sOperator)
|
||||
{
|
||||
case '=' : $result = ($left == $right); break;
|
||||
case '!=' : $result = ($left != $right); break;
|
||||
case '<>' : $result = ($left != $right); break;
|
||||
default:
|
||||
throw new Exception("Logic: unknown operator '$sOperator'");
|
||||
}
|
||||
break;
|
||||
case 'numcomp':
|
||||
$iLeft = static::ComparableValue($mLeft);
|
||||
$iRight = static::ComparableValue($mRight);
|
||||
switch ($sOperator)
|
||||
{
|
||||
case '=' : $result = ($iLeft == $iRight); break;
|
||||
case '>' : $result = ($iLeft > $iRight); break;
|
||||
case '<' : $result = ($iLeft < $iRight); break;
|
||||
case '>=' : $result = ($iLeft >= $iRight); break;
|
||||
case '<=' : $result = ($iLeft <= $iRight); break;
|
||||
case '!=' : $result = ($iLeft != $iRight); break;
|
||||
case '<>' : $result = ($iLeft != $iRight); break;
|
||||
default:
|
||||
throw new Exception("Logic: unknown operator '$sOperator'");
|
||||
}
|
||||
break;
|
||||
case 'like':
|
||||
$sEscaped = preg_quote($mRight, '/');
|
||||
$sEscaped = str_replace(array('%', '_', '\\\\.*', '\\\\.'), array('.*', '.', '%', '_'), $sEscaped);
|
||||
$result = (int) preg_match("/$sEscaped/i", $mLeft);
|
||||
break;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
static protected function CastToBool($mValue)
|
||||
{
|
||||
if (is_string($mValue))
|
||||
{
|
||||
if (is_numeric($mValue))
|
||||
{
|
||||
return abs($mValue) > 0;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return (bool)$mValue;
|
||||
}
|
||||
static protected function ComparableValue($mixed)
|
||||
{
|
||||
if (is_string($mixed))
|
||||
{
|
||||
$oDate = new \DateTime($mixed);
|
||||
if (($oDate->format('Y-m-d') == $mixed) || ($oDate->format('Y-m-d H:i:s') == $mixed))
|
||||
{
|
||||
$iRet = $oDate->format('U');
|
||||
}
|
||||
else
|
||||
{
|
||||
$iRet = (int) $mixed;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$iRet = $mixed;
|
||||
}
|
||||
|
||||
return $iRet;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* @throws \MissingQueryArgument
|
||||
@@ -865,6 +1062,16 @@ class MatchExpression extends BinaryExpression
|
||||
return $sRet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluate the value of the expression
|
||||
* @param array $aArgs
|
||||
* @throws \Exception if terms cannot be evaluated as scalars
|
||||
*/
|
||||
public function Evaluate(array $aArgs)
|
||||
{
|
||||
throw new Exception('evaluation of MATCHES not implemented yet');
|
||||
}
|
||||
|
||||
public function Translate($aTranslationData, $bMatchAll = true, $bMarkFieldsAsResolved = true)
|
||||
{
|
||||
/** @var \FieldExpression $oLeft */
|
||||
@@ -903,6 +1110,16 @@ class UnaryExpression extends Expression
|
||||
return CMDBSource::Quote($this->m_value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluate the value of the expression
|
||||
* @param array $aArgs
|
||||
* @throws \Exception if terms cannot be evaluated as scalars
|
||||
*/
|
||||
public function Evaluate(array $aArgs)
|
||||
{
|
||||
return $this->m_value;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* @throws \MissingQueryArgument
|
||||
@@ -1049,6 +1266,16 @@ class ScalarExpression extends UnaryExpression
|
||||
return $sRet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluate the value of the expression
|
||||
* @param array $aArgs
|
||||
* @throws \Exception if terms cannot be evaluated as scalars
|
||||
*/
|
||||
public function Evaluate(array $aArgs)
|
||||
{
|
||||
return $this->m_value;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* @see Expression::ToJSON()
|
||||
@@ -1357,6 +1584,21 @@ class FieldExpression extends UnaryExpression
|
||||
return "`{$this->m_sParent}`.`{$this->m_sName}`";
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluate the value of the expression
|
||||
* @param array $aArgs
|
||||
* @throws \Exception if terms cannot be evaluated as scalars
|
||||
*/
|
||||
public function Evaluate(array $aArgs)
|
||||
{
|
||||
$sKey = empty($this->m_sParent) ? $this->m_sName : "{$this->m_sParent}.{$this->m_sName}";
|
||||
if (!array_key_exists($sKey, $aArgs))
|
||||
{
|
||||
throw new Exception("Missing field '$sKey' from context");
|
||||
}
|
||||
return $aArgs[$sKey];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* @see Expression::ToJSON()
|
||||
@@ -1412,7 +1654,8 @@ class FieldExpression extends UnaryExpression
|
||||
|
||||
public function ListRequiredFields()
|
||||
{
|
||||
return array($this->m_sParent.'.'.$this->m_sName);
|
||||
$sField = empty($this->m_sParent) ? $this->m_sName : "{$this->m_sParent}.{$this->m_sName}";
|
||||
return array($sField);
|
||||
}
|
||||
|
||||
public function CollectUsedParents(&$aTable)
|
||||
@@ -1793,6 +2036,16 @@ class VariableExpression extends UnaryExpression
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluate the value of the expression
|
||||
* @param array $aArgs
|
||||
* @throws \Exception if terms cannot be evaluated as scalars
|
||||
*/
|
||||
public function Evaluate(array $aArgs)
|
||||
{
|
||||
throw new Exception('not implemented yet');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* @see Expression::ToJSON()
|
||||
@@ -1933,6 +2186,16 @@ class ListExpression extends Expression
|
||||
return '('.implode(', ', $aRes).')';
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluate the value of the expression
|
||||
* @param array $aArgs
|
||||
* @throws \Exception if terms cannot be evaluated as scalars
|
||||
*/
|
||||
public function Evaluate(array $aArgs)
|
||||
{
|
||||
throw new Exception('list expression not yet supported');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* @see Expression::ToJSON()
|
||||
@@ -2140,6 +2403,16 @@ class NestedQueryExpression extends Expression
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluate the value of the expression
|
||||
* @param array $aArgs
|
||||
* @throws \Exception if terms cannot be evaluated as scalars
|
||||
*/
|
||||
public function Evaluate(array $aArgs)
|
||||
{
|
||||
throw new Exception('a nested query cannot be evaluated');
|
||||
}
|
||||
|
||||
public function Browse(Closure $callback)
|
||||
{
|
||||
$callback($this);
|
||||
@@ -2240,6 +2513,252 @@ class FunctionExpression extends Expression
|
||||
return $this->m_sVerb.'('.implode(', ', $aRes).')';
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluate the value of the expression
|
||||
* @param array $aArgs
|
||||
* @throws \Exception if terms cannot be evaluated as scalars
|
||||
*/
|
||||
public function Evaluate(array $aArgs)
|
||||
{
|
||||
switch($this->m_sVerb)
|
||||
{
|
||||
case 'CONCAT':
|
||||
$sRet = '';
|
||||
foreach ($this->m_aArgs as $iPos => $oExpr)
|
||||
{
|
||||
$item = $oExpr->Evaluate($aArgs);
|
||||
if (is_null($item)) return null;
|
||||
$sRet .= $item;
|
||||
}
|
||||
return $sRet;
|
||||
|
||||
case 'CONCAT_WS':
|
||||
if (count($this->m_aArgs) < 3)
|
||||
{
|
||||
throw new \Exception("Function {$this->m_sVerb} requires at least 3 arguments");
|
||||
}
|
||||
$sSeparator = $this->m_aArgs[0]->Evaluate($aArgs);
|
||||
foreach ($this->m_aArgs as $iPos => $oExpr)
|
||||
{
|
||||
if ($iPos == 0) continue;
|
||||
$item = $oExpr->Evaluate($aArgs);
|
||||
if (is_null($item)) return null;
|
||||
$aStrings[] = $item;
|
||||
}
|
||||
$sRet = implode($sSeparator, $aStrings);
|
||||
return $sRet;
|
||||
|
||||
case 'SUBSTR':
|
||||
if (count($this->m_aArgs) < 2)
|
||||
{
|
||||
throw new \Exception("Function {$this->m_sVerb} requires at least 2 arguments");
|
||||
}
|
||||
$sString = $this->m_aArgs[0]->Evaluate($aArgs);
|
||||
$iRawPos = $this->m_aArgs[1]->Evaluate($aArgs);
|
||||
$iPos = $iRawPos > 0 ?
|
||||
$iRawPos - 1// 0-based in PHP (1-based in SQL)
|
||||
: $iRawPos; // Negative
|
||||
if (count($this->m_aArgs) == 2)
|
||||
{
|
||||
// Up to the end of the string
|
||||
$sRet = substr($sString, $iPos);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Length specified
|
||||
$iLen = $this->m_aArgs[2]->Evaluate($aArgs);
|
||||
$sRet = substr($sString, $iPos, $iLen);
|
||||
}
|
||||
return $sRet;
|
||||
|
||||
case 'TRIM':
|
||||
if (count($this->m_aArgs) != 1)
|
||||
{
|
||||
throw new \Exception("Function {$this->m_sVerb} requires 1 argument");
|
||||
}
|
||||
$sRet = trim($this->m_aArgs[0]->Evaluate($aArgs));
|
||||
return $sRet;
|
||||
|
||||
case 'INET_ATON':
|
||||
if (count($this->m_aArgs) != 1)
|
||||
{
|
||||
throw new \Exception("Function {$this->m_sVerb} requires 1 argument");
|
||||
}
|
||||
$sRet = ip2long($this->m_aArgs[0]->Evaluate($aArgs));
|
||||
return $sRet;
|
||||
|
||||
case 'INET_NTOA':
|
||||
if (count($this->m_aArgs) != 1)
|
||||
{
|
||||
throw new \Exception("Function {$this->m_sVerb} requires 1 argument");
|
||||
}
|
||||
$sRet = long2ip($this->m_aArgs[0]->Evaluate($aArgs));
|
||||
return $sRet;
|
||||
|
||||
case 'ISNULL':
|
||||
if (count($this->m_aArgs) != 1)
|
||||
{
|
||||
throw new \Exception("Function {$this->m_sVerb} requires 1 argument");
|
||||
}
|
||||
$sRet = is_null($this->m_aArgs[0]->Evaluate($aArgs));
|
||||
return $sRet;
|
||||
|
||||
case 'COALESCE':
|
||||
if (count($this->m_aArgs) < 1)
|
||||
{
|
||||
throw new \Exception("Function {$this->m_sVerb} requires at least 1 argument");
|
||||
}
|
||||
$ret = null;
|
||||
foreach($this->m_aArgs as $iPos => $oExpr)
|
||||
{
|
||||
$ret = $oExpr->Evaluate($aArgs);
|
||||
if (!is_null($ret)) break;
|
||||
}
|
||||
return $ret;
|
||||
|
||||
case 'IF':
|
||||
if (count($this->m_aArgs) != 3)
|
||||
{
|
||||
throw new \Exception("Function {$this->m_sVerb} requires 3 arguments");
|
||||
}
|
||||
$bCond = $this->m_aArgs[0]->Evaluate($aArgs);
|
||||
if ($bCond)
|
||||
{
|
||||
$ret = $this->m_aArgs[1]->Evaluate($aArgs);
|
||||
}
|
||||
else
|
||||
{
|
||||
$ret = $this->m_aArgs[2]->Evaluate($aArgs);
|
||||
}
|
||||
return $ret;
|
||||
|
||||
case 'ELT':
|
||||
if (count($this->m_aArgs) < 2)
|
||||
{
|
||||
throw new \Exception("Function {$this->m_sVerb} requires at least 2 arguments");
|
||||
}
|
||||
// First argument is the 1-based position
|
||||
$iPosition = (int) $this->m_aArgs[0]->Evaluate($aArgs);
|
||||
if (($iPosition == 0) || ($iPosition >= count($this->m_aArgs)))
|
||||
{
|
||||
// Out of range
|
||||
$ret = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
$ret = $this->m_aArgs[$iPosition]->Evaluate($aArgs);
|
||||
}
|
||||
return $ret;
|
||||
|
||||
case 'DATE':
|
||||
if (count($this->m_aArgs) != 1)
|
||||
{
|
||||
throw new \Exception("Function {$this->m_sVerb} requires 1 argument");
|
||||
}
|
||||
$sRet = date('Y-m-d', strtotime($this->m_aArgs[0]->Evaluate($aArgs)));
|
||||
return $sRet;
|
||||
|
||||
case 'YEAR':
|
||||
if (count($this->m_aArgs) != 1)
|
||||
{
|
||||
throw new \Exception("Function {$this->m_sVerb} requires 1 argument");
|
||||
}
|
||||
$iRet = (int) date('Y', strtotime($this->m_aArgs[0]->Evaluate($aArgs)));
|
||||
return $iRet;
|
||||
|
||||
case 'MONTH':
|
||||
if (count($this->m_aArgs) != 1)
|
||||
{
|
||||
throw new \Exception("Function {$this->m_sVerb} requires 1 argument");
|
||||
}
|
||||
$iRet = (int) date('m', strtotime($this->m_aArgs[0]->Evaluate($aArgs)));
|
||||
return $iRet;
|
||||
|
||||
case 'DAY':
|
||||
if (count($this->m_aArgs) != 1)
|
||||
{
|
||||
throw new \Exception("Function {$this->m_sVerb} requires 1 argument");
|
||||
}
|
||||
$iRet = (int) date('d', strtotime($this->m_aArgs[0]->Evaluate($aArgs)));
|
||||
return $iRet;
|
||||
|
||||
case 'DATE_FORMAT':
|
||||
if (count($this->m_aArgs) != 2)
|
||||
{
|
||||
throw new \Exception("Function {$this->m_sVerb} requires 2 arguments");
|
||||
}
|
||||
$oDate = new DateTime($this->m_aArgs[0]->Evaluate($aArgs));
|
||||
$sFormat = $this->m_aArgs[1]->Evaluate($aArgs);
|
||||
$sFormat = str_replace(
|
||||
array('%y', '%x', '%w', '%W', '%v', '%T', '%S', '%r', '%p', '%M', '%l', '%k', '%I', '%h', '%b', '%a', '%D', '%c', '%e', '%Y', '%d', '%m', '%H', '%i', '%s'),
|
||||
array('y', 'o', 'w', 'l', 'W', 'H:i:s', 's', 'h:i:s A', 'A', 'F', 'g', 'H', 'h', 'h','M', 'D', 'jS', 'n', 'j', 'Y', 'd', 'm', 'H', 'i', 's'),
|
||||
$sFormat);
|
||||
if (preg_match('/%j/', $sFormat))
|
||||
{
|
||||
$sFormat = str_replace('%j', date_format($oDate, 'z') + 1, $sFormat);
|
||||
}
|
||||
if (preg_match('/%[fUuVX]/', $sFormat))
|
||||
{
|
||||
throw new NotYetEvaluatedExpression("Expression ".$this->RenderExpression().' cannot be evaluated (known limitation)');
|
||||
}
|
||||
$sRet = date_format($oDate, $sFormat);
|
||||
return $sRet;
|
||||
|
||||
case 'TO_DAYS':
|
||||
if (count($this->m_aArgs) != 1)
|
||||
{
|
||||
throw new \Exception("Function {$this->m_sVerb} requires 1 argument");
|
||||
}
|
||||
$oDate = new DateTime($this->m_aArgs[0]->Evaluate($aArgs));
|
||||
$oZero = new DateTime('1582-01-01');
|
||||
$iRet = (int) $oDate->diff($oZero)->format('%a') + 577815;
|
||||
return $iRet;
|
||||
|
||||
case 'FROM_DAYS':
|
||||
if (count($this->m_aArgs) != 1)
|
||||
{
|
||||
throw new \Exception("Function {$this->m_sVerb} requires 1 argument");
|
||||
}
|
||||
$iSince1582 = $this->m_aArgs[0]->Evaluate($aArgs) - 577814;
|
||||
$oDate = new DateTime("1582-01-01 +$iSince1582 days");
|
||||
$sRet = $oDate->format('Y-m-d');
|
||||
return $sRet;
|
||||
|
||||
case 'NOW':
|
||||
$sRet = date('Y-m-d H:i:s');
|
||||
return $sRet;
|
||||
|
||||
case 'CURRENT_DATE':
|
||||
$sRet = date('Y-m-d');
|
||||
return $sRet;
|
||||
|
||||
case 'DATE_ADD':
|
||||
if (count($this->m_aArgs) != 2)
|
||||
{
|
||||
throw new \Exception("Function {$this->m_sVerb} requires 2 arguments");
|
||||
}
|
||||
$sStartDate = $this->m_aArgs[0]->Evaluate($aArgs);
|
||||
$sInterval = $this->m_aArgs[1]->Evaluate($aArgs);
|
||||
$oDate = new DateTime("$sStartDate +$sInterval");
|
||||
$sRet = $oDate->format('Y-m-d H:i:s');
|
||||
return $sRet;
|
||||
|
||||
case 'DATE_SUB':
|
||||
if (count($this->m_aArgs) != 2)
|
||||
{
|
||||
throw new \Exception("Function {$this->m_sVerb} requires 2 arguments");
|
||||
}
|
||||
$sStartDate = $this->m_aArgs[0]->Evaluate($aArgs);
|
||||
$sInterval = $this->m_aArgs[1]->Evaluate($aArgs);
|
||||
$oDate = new DateTime("$sStartDate -$sInterval");
|
||||
$sRet = $oDate->format('Y-m-d H:i:s');
|
||||
return $sRet;
|
||||
|
||||
default:
|
||||
throw new Exception("Function {$this->m_sVerb} cannot be evaluated -unhandled yet");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* @see Expression::ToJSON()
|
||||
@@ -2566,6 +3085,17 @@ class IntervalExpression extends Expression
|
||||
return 'INTERVAL '.$this->m_oValue->RenderExpression($bForSQL, $aArgs, $bRetrofitParams).' '.$this->m_sUnit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluate the value of the expression
|
||||
* @param array $aArgs
|
||||
* @throws \Exception if terms cannot be evaluated as scalars
|
||||
*/
|
||||
public function Evaluate(array $aArgs)
|
||||
{
|
||||
$iValue = $this->m_oValue->Evaluate($aArgs);
|
||||
return "$iValue {$this->m_sUnit}";
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* @see Expression::ToJSON()
|
||||
@@ -2684,6 +3214,21 @@ class CharConcatExpression extends Expression
|
||||
return "CAST(CONCAT(".implode(', ', $aRes).") AS CHAR)";
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluate the value of the expression
|
||||
* @param array $aArgs
|
||||
* @throws \Exception if terms cannot be evaluated as scalars
|
||||
*/
|
||||
public function Evaluate(array $aArgs)
|
||||
{
|
||||
$sRet = '';
|
||||
foreach ($this->m_aExpressions as $oExpr)
|
||||
{
|
||||
$sRet .= $oExpr->Evaluate($aArgs);
|
||||
}
|
||||
return $sRet;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* @see Expression::ToJSON()
|
||||
@@ -2825,6 +3370,21 @@ class CharConcatWSExpression extends CharConcatExpression
|
||||
return "CAST(CONCAT_WS($sSep, ".implode(', ', $aRes).") AS CHAR)";
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluate the value of the expression
|
||||
* @param array $aArgs
|
||||
* @throws \Exception if terms cannot be evaluated as scalars
|
||||
*/
|
||||
public function Evaluate(array $aArgs)
|
||||
{
|
||||
$aRes = array();
|
||||
foreach ($this->m_aExpressions as $oExpr)
|
||||
{
|
||||
$aRes .= $oExpr->Evaluate($aArgs);
|
||||
}
|
||||
return implode($this->m_separator, $aRes);
|
||||
}
|
||||
|
||||
public function Browse(Closure $callback)
|
||||
{
|
||||
$callback($this);
|
||||
|
||||
@@ -610,9 +610,6 @@
|
||||
</field>
|
||||
<field id="manager_id" xsi:type="AttributeExternalKey">
|
||||
<filter><![CDATA[SELECT Person]]></filter>
|
||||
<dependencies>
|
||||
<attribute id="org_id"/>
|
||||
</dependencies>
|
||||
<sql>manager_id</sql>
|
||||
<target_class>Person</target_class>
|
||||
<is_null_allowed>true</is_null_allowed>
|
||||
|
||||
@@ -713,7 +713,7 @@ class ItopDataTestCase extends ItopTestCase
|
||||
$iId = $oLnk->Get('functionalci_id');
|
||||
if (!empty($aWaitedCIList))
|
||||
{
|
||||
$this->assertTrue(array_key_exists($iId, $aWaitedCIList));
|
||||
$this->assertArrayHasKey($iId, $aWaitedCIList);
|
||||
$this->assertEquals($aWaitedCIList[$iId], $oLnk->Get('impact_code'));
|
||||
}
|
||||
}
|
||||
@@ -737,7 +737,7 @@ class ItopDataTestCase extends ItopTestCase
|
||||
$iId = $oLnk->Get('contact_id');
|
||||
if (!empty($aWaitedContactList))
|
||||
{
|
||||
$this->assertTrue(array_key_exists($iId, $aWaitedContactList));
|
||||
$this->assertArrayHasKey($iId, $aWaitedContactList);
|
||||
foreach ($aWaitedContactList[$iId] as $sAttCode => $oValue)
|
||||
{
|
||||
if (MetaModel::IsValidAttCode(get_class($oTicket), $sAttCode))
|
||||
@@ -756,5 +756,29 @@ class ItopDataTestCase extends ItopTestCase
|
||||
$this->iTestOrgId = $oOrg->GetKey();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Assert that a series of operations will trigger a given number of MySL queries
|
||||
*
|
||||
* @param $iExpectedCount Number of MySQL queries that should be executed
|
||||
* @param callable $oFunction Operations to perform
|
||||
*
|
||||
* @throws \MySQLException
|
||||
* @throws \MySQLQueryHasNoResultException
|
||||
*/
|
||||
protected static function assertDBQueryCount($iExpectedCount, callable $oFunction)
|
||||
{
|
||||
$iInitialCount = (int) CMDBSource::QueryToScalar("SHOW SESSION STATUS LIKE 'Queries'", 1);
|
||||
$oFunction();
|
||||
$iFinalCount = (int) CMDBSource::QueryToScalar("SHOW SESSION STATUS LIKE 'Queries'", 1);
|
||||
$iCount = $iFinalCount - 1 - $iInitialCount;
|
||||
if ($iCount != $iExpectedCount)
|
||||
{
|
||||
static::fail("Expected $iExpectedCount queries. $iCount have been executed.");
|
||||
}
|
||||
else
|
||||
{
|
||||
// Otherwise PHP Unit will consider that no assertion has been made
|
||||
static::assertTrue(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,6 +48,7 @@ class DBObjectTest extends ItopDataTestCase
|
||||
|
||||
/**
|
||||
* Test default page name
|
||||
* @covers DBObject::GetUIPage
|
||||
*/
|
||||
public function testGetUIPage()
|
||||
{
|
||||
@@ -81,6 +82,9 @@ class DBObjectTest extends ItopDataTestCase
|
||||
array('PHP_INT_MIN', false));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers DBObject::GetOriginal
|
||||
*/
|
||||
public function testGetOriginal()
|
||||
{
|
||||
$oObject = $this->CreateUserRequest(190664);
|
||||
@@ -88,4 +92,114 @@ class DBObjectTest extends ItopDataTestCase
|
||||
static::assertNull($oObject->GetOriginal('sla_tto_passed'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers DBObject::NewObject
|
||||
* @covers DBObject::Get
|
||||
* @covers DBObject::Set
|
||||
*/
|
||||
public function testAttributeRefresh_FriendlyName()
|
||||
{
|
||||
$oObject = \MetaModel::NewObject('Person', array('name' => 'Foo', 'first_name' => 'John', 'org_id' => 3, 'location_id' => 2));
|
||||
|
||||
static::assertEquals('John Foo', $oObject->Get('friendlyname'));
|
||||
$oObject->Set('name', 'Who');
|
||||
static::assertEquals('John Who', $oObject->Get('friendlyname'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers MetaModel::GetObject
|
||||
* @covers DBObject::Get
|
||||
* @covers DBObject::Set
|
||||
*/
|
||||
public function testAttributeRefresh_FriendlyNameFromDB()
|
||||
{
|
||||
$oObject = \MetaModel::NewObject('Person', array('name' => 'Gary', 'first_name' => 'Romain', 'org_id' => 3, 'location_id' => 2));
|
||||
$oObject->DBInsert();
|
||||
$iObjKey = $oObject->GetKey();
|
||||
|
||||
$oObject = \MetaModel::GetObject('Person', $iObjKey);
|
||||
|
||||
static::assertEquals('Romain Gary', $oObject->Get('friendlyname'));
|
||||
$oObject->Set('name', 'Duris');
|
||||
static::assertEquals('Romain Duris', $oObject->Get('friendlyname'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers DBObject::NewObject
|
||||
* @covers DBObject::Get
|
||||
* @covers DBObject::Set
|
||||
*/
|
||||
public function testAttributeRefresh_ObsolescenceFlag()
|
||||
{
|
||||
$oObject = \MetaModel::NewObject('Person', array('name' => 'Foo', 'first_name' => 'John', 'org_id' => 3, 'location_id' => 2));
|
||||
|
||||
static::assertEquals(false, (bool)$oObject->Get('obsolescence_flag'));
|
||||
$oObject->Set('status', 'inactive');
|
||||
static::assertEquals(true, (bool)$oObject->Get('obsolescence_flag'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers DBObject::NewObject
|
||||
* @covers DBObject::Get
|
||||
* @covers DBObject::Set
|
||||
*/
|
||||
public function testAttributeRefresh_ExternalKeysAndFields()
|
||||
{
|
||||
static::assertDBQueryCount(0, function() use (&$oObject){
|
||||
$oObject = \MetaModel::NewObject('Person', array('name' => 'Foo', 'first_name' => 'John', 'org_id' => 3, 'location_id' => 2));
|
||||
});
|
||||
static::assertDBQueryCount(2, function() use (&$oObject){
|
||||
static::assertEquals('Demo', $oObject->Get('org_id_friendlyname'));
|
||||
static::assertEquals('Grenoble', $oObject->Get('location_id_friendlyname'));
|
||||
});
|
||||
|
||||
// External key given as an id
|
||||
static::assertDBQueryCount(1, function() use (&$oObject){
|
||||
$oObject->Set('org_id', 2);
|
||||
static::assertEquals('IT Department', $oObject->Get('org_id_friendlyname'));
|
||||
});
|
||||
|
||||
// External key given as an object
|
||||
static::assertDBQueryCount(1, function() use (&$oBordeaux){
|
||||
$oBordeaux = \MetaModel::GetObject('Location', 1);
|
||||
});
|
||||
|
||||
static::assertDBQueryCount(0, function() use (&$oBordeaux, &$oObject){
|
||||
$oObject->Set('location_id', $oBordeaux);
|
||||
static::assertEquals('IT Department', $oObject->Get('org_id_friendlyname'));
|
||||
static::assertEquals('IT Department', $oObject->Get('org_name'));
|
||||
static::assertEquals('Bordeaux', $oObject->Get('location_id_friendlyname'));
|
||||
});
|
||||
}
|
||||
|
||||
public function testSetExtKeyUnsetDependentAttribute()
|
||||
{
|
||||
$oObject = \MetaModel::NewObject('Person', array('name' => 'Foo', 'first_name' => 'John', 'org_id' => 3, 'location_id' => 2));
|
||||
$oOrg = \MetaModel::GetObject('Organization', 2);
|
||||
$oObject->Set('org_id', $oOrg);
|
||||
static::assertEquals(0, $oObject->Get('location_id'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @group Integration
|
||||
*/
|
||||
public function testModelExpressions()
|
||||
{
|
||||
foreach (\MetaModel::GetClasses() as $sClass)
|
||||
{
|
||||
if (\MetaModel::IsAbstract($sClass)) continue;
|
||||
|
||||
$oObject = \MetaModel::NewObject($sClass);
|
||||
foreach (\MetaModel::ListAttributeDefs($sClass) as $sAttCode => $oAttDef)
|
||||
{
|
||||
if ($oAttDef->IsBasedOnOQLExpression())
|
||||
{
|
||||
$this->debug("$sClass::$sAttCode");
|
||||
static::assertDBQueryCount(0, function() use (&$oObject, &$oAttDef){
|
||||
$oObject->EvaluateExpression($oAttDef->GetOQLExpression());
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
535
test/core/ExpressionEvaluateTest.php
Normal file
535
test/core/ExpressionEvaluateTest.php
Normal file
@@ -0,0 +1,535 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace Combodo\iTop\Test\UnitTest\Core;
|
||||
|
||||
|
||||
use CMDBSource;
|
||||
use Combodo\iTop\Test\UnitTest\iTopDataTestCase;
|
||||
use DateInterval;
|
||||
use DateTime;
|
||||
use Expression;
|
||||
use FunctionExpression;
|
||||
use MetaModel;
|
||||
use ScalarExpression;
|
||||
|
||||
class ExpressionEvaluateTest extends iTopDataTestCase
|
||||
{
|
||||
const USE_TRANSACTION = false;
|
||||
|
||||
/**
|
||||
* @covers Expression::GetParameters()
|
||||
* @dataProvider GetParametersProvider
|
||||
*
|
||||
* @param $sExpression
|
||||
* @param $sParentFilter
|
||||
* @param $aExpectedParameters
|
||||
*
|
||||
* @throws \OQLException
|
||||
*/
|
||||
public function testGetParameters($sExpression, $sParentFilter, $aExpectedParameters)
|
||||
{
|
||||
$oExpression = Expression::FromOQL($sExpression);
|
||||
$aParameters = $oExpression->GetParameters($sParentFilter);
|
||||
sort($aExpectedParameters);
|
||||
sort($aParameters);
|
||||
static::assertEquals($aExpectedParameters, $aParameters);
|
||||
}
|
||||
|
||||
public function GetParametersProvider()
|
||||
{
|
||||
return array(
|
||||
array('1 AND 0 OR :hello + :world', null, array('hello', 'world')),
|
||||
array('1 AND 0 OR :hello + :world', 'this', array()),
|
||||
array(':this->left + :this->right', null, array('this->left', 'this->right')),
|
||||
array(':this->left + :this->right', 'this', array('left', 'right')),
|
||||
array(':this->left + :this->right', 'that', array()),
|
||||
array(':this_left + :this_right', 'this', array()),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 100x quicker to execute than testExpressionEvaluate
|
||||
*
|
||||
* @covers Expression::Evaluate()
|
||||
* @covers Expression::FromOQL()
|
||||
* @relies-on-dataProvider VariousExpressions
|
||||
* @throws \OQLException
|
||||
*/
|
||||
public function _testExpressionEvaluateAllAtOnce()
|
||||
{
|
||||
$aTestCases = $this->VariousExpressionsProvider();
|
||||
foreach ($aTestCases as $sCaseId => $aTestArgs)
|
||||
{
|
||||
$this->debug("Case $sCaseId:");
|
||||
$this->testVariousExpressions($aTestArgs[0], $aTestArgs[1]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers Expression::Evaluate()
|
||||
* @covers Expression::FromOQL()
|
||||
* @dataProvider VariousExpressionsProvider
|
||||
*
|
||||
* @param string $sExpression
|
||||
* @param string $expectedValue
|
||||
*
|
||||
* @throws \OQLException
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function testVariousExpressions($sExpression, $expectedValue)
|
||||
{
|
||||
$oExpression = Expression::FromOQL($sExpression);
|
||||
$value = $oExpression->Evaluate(array());
|
||||
static::assertEquals($expectedValue, $value);
|
||||
}
|
||||
|
||||
public function VariousExpressionsProvider()
|
||||
{
|
||||
if (false)
|
||||
{
|
||||
$aExpressions = array(
|
||||
// Test case to isolate for troubleshooting purposes
|
||||
array('1+1', 2),
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
$aExpressions = array(
|
||||
// The bare minimum
|
||||
array('"blah"', 'blah'),
|
||||
array('"\\\\"', '\\'),
|
||||
// Arithmetics
|
||||
array('2+2', 4),
|
||||
array('2+2-2', 2),
|
||||
array('2*(3+4)', 14),
|
||||
array('(2*3)+4', 10),
|
||||
array('2*3+4', 10),
|
||||
// Strings
|
||||
array("CONCAT('hello', 'world')", 'helloworld'),
|
||||
// Not yet parsed - array("CONCAT_WS(' ', 'hello', 'world')", 'hello world'),
|
||||
array("SUBSTR('abcdef', 2, 3)", 'bcd'),
|
||||
array("TRIM(' Sin dolor ')", 'Sin dolor'),
|
||||
// Comparison operators
|
||||
array('1 = 1', 1),
|
||||
array('1 != 1', 0),
|
||||
array('0 = 1', 0),
|
||||
array('0 != 1', 1),
|
||||
array('2 > 1', 1),
|
||||
array('2 < 1', 0),
|
||||
array('1 > 2', 0),
|
||||
array('2 > 1', 1),
|
||||
array('2 >= 1', 1),
|
||||
array('2 >= 2', 1),
|
||||
array("'the quick brown dog' LIKE '%QUICK%'", 1),
|
||||
array("'the quick brown dog' LIKE '%SLOW%'", 0),
|
||||
array("'the quick brown dog' LIKE '%QU_CK%'", 1),
|
||||
array("'the quick brown dog' LIKE '%QU_ICK%'", 0),
|
||||
array('"400 (km/h)" LIKE "400%"', 1),
|
||||
array('"400 (km/h)" LIKE "100%"', 0),
|
||||
array('"2020-06-12" > "2020-06-11"', 1),
|
||||
array('"2020-06-12" < "2020-06-11"', 0),
|
||||
array('" 2020-06-12" > "2020-06-11"', 0), // Leading spaces => a string
|
||||
array('" 2020-06-12 " > "2020-06-11"', 0), // Trailing spaces => a string
|
||||
array('"2020-06-12 17:35:13" > "2020-06-12 17:35:12"', 1),
|
||||
array('"2020-06-12 17:35:13" < "2020-06-12 17:35:12"', 0),
|
||||
array('"2020-06-12 17:35:13" > "2020-06-12"', 1),
|
||||
array('"2020-06-12 17:35:13" < "2020-06-12"', 0),
|
||||
array('"2020-06-12 00:00:00" = "2020-06-12"', 0),
|
||||
// Logical operators
|
||||
array('0 AND 0', 0),
|
||||
array('1 AND 0', 0),
|
||||
array('0 AND 1', 0),
|
||||
array('1 AND 1', 1),
|
||||
array('0 OR 0', 0),
|
||||
array('0 OR 1', 1),
|
||||
array('1 OR 0', 1),
|
||||
array('1 OR 1', 1),
|
||||
array('1 AND 0 OR 1', 1),
|
||||
// Casting
|
||||
array('1 AND "blah"', 0),
|
||||
array('1 AND "1"', 1),
|
||||
array('1 AND "2"', 1),
|
||||
array('1 AND "0"', 0),
|
||||
array('1 AND "-1"', 1),
|
||||
// Null
|
||||
array('NULL', null),
|
||||
array('1 AND NULL', null),
|
||||
array('CONCAT("Great but...", NULL)', null),
|
||||
array('COALESCE(NULL, 123)', 123),
|
||||
array('COALESCE(321, 123)', 321),
|
||||
array('ISNULL(NULL)', 1),
|
||||
array('ISNULL(123)', 0),
|
||||
// Date functions
|
||||
array("DATE('2020-03-12 13:18:30')", '2020-03-12'),
|
||||
array("DATE_FORMAT('2009-10-04 22:23:00', '%Y %m %d %H %i %s')", '2009 10 04 22 23 00'),
|
||||
array("DATE(NOW()) = CURRENT_DATE()", 1), // Could fail if executed around midnight!
|
||||
array("TO_DAYS('2020-01-02')", 737791),
|
||||
array("FROM_DAYS(737791)", '2020-01-02'),
|
||||
array("YEAR('2020-05-03')", 2020),
|
||||
array("MONTH('2020-05-03')", 5),
|
||||
array("DAY('2020-05-03')", 3),
|
||||
array("DATE_ADD('2020-02-28 18:00:00', INTERVAL 1 HOUR)", '2020-02-28 19:00:00'),
|
||||
array("DATE_ADD('2020-02-28 18:00:00', INTERVAL 1 DAY)", '2020-02-29 18:00:00'),
|
||||
array("DATE_SUB('2020-03-01 18:00:00', INTERVAL 1 HOUR)", '2020-03-01 17:00:00'),
|
||||
array("DATE_SUB('2020-03-01 18:00:00', INTERVAL 1 DAY)", '2020-02-29 18:00:00'),
|
||||
// Misc. functions
|
||||
array('IF(1, 123, 567)', 123),
|
||||
array('IF(0, 123, 567)', 567),
|
||||
array('ELT(3, "a", "b", "c")', 'c'),
|
||||
array('ELT(0, "a", "b", "c")', null),
|
||||
array('ELT(4, "a", "b", "c")', null),
|
||||
array('INET_ATON("128.0.0.1")', 2147483649),
|
||||
array('INET_NTOA(2147483649)', '128.0.0.1'),
|
||||
);
|
||||
}
|
||||
|
||||
// Build a comprehensive index
|
||||
$aRet = array();
|
||||
foreach ($aExpressions as $aExp)
|
||||
{
|
||||
$aRet[$aExp[0]] = $aExp;
|
||||
}
|
||||
return $aRet;
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers Expression::Evaluate()
|
||||
* @dataProvider NotYetParsableExpressionsProvider
|
||||
*
|
||||
* @param string $sExpression
|
||||
* @param string $expectedValue
|
||||
*/
|
||||
public function testNotYetParsableExpressions($sExpression, $expectedValue)
|
||||
{
|
||||
$sNewExpression = "return $sExpression;";
|
||||
$oExpression = eval($sNewExpression);
|
||||
$res = $oExpression->Evaluate(array());
|
||||
static::assertEquals($expectedValue, $res);
|
||||
}
|
||||
|
||||
public function NotYetParsableExpressionsProvider()
|
||||
{
|
||||
$aExpressions = array(
|
||||
array("new \\FunctionExpression('CONCAT_WS', array(new \\ScalarExpression(' '), new \\ScalarExpression('Hello'), new \ScalarExpression('world!')))", 'Hello world!'),
|
||||
array("new \\ScalarExpression('windows\\system32')", 'windows\\system32'),
|
||||
array("new \\BinaryExpression(new \\ScalarExpression('100%'), 'LIKE', new \\ScalarExpression('___\%'))", 1),
|
||||
array("new \\BinaryExpression(new \ScalarExpression('1000'), 'LIKE', new \ScalarExpression('___\%'))", 0),
|
||||
// Net yet parsed - array("TIME(NOW()) = CURRENT_TIME()", 1), // Not relevant
|
||||
// Not yet parsed - array("DATE_ADD('2020-02-28 18:00:00', INTERVAL 1 WEEK)", '2020-03-06 18:00:00'),
|
||||
// Not yet parsed - array("DATE_SUB('2020-03-01 18:00:00', INTERVAL 1 WEEK)", '2020-02-23 18:00:00'),
|
||||
// Not yet parsed - array('ROUND(1.2345, 2)', 1.23),
|
||||
// Not yet parsed - array('FLOOR(1.2)', 1),
|
||||
);
|
||||
// Build a comprehensive index
|
||||
$aRet = array();
|
||||
foreach ($aExpressions as $aExp)
|
||||
{
|
||||
$aRet[$aExp[0]] = $aExp;
|
||||
}
|
||||
return $aRet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that the test data would give the same result when evaluated by MySQL
|
||||
* It uses the data provider ExpressionProvider, and checks every test case in one single query
|
||||
*
|
||||
* @throws \MySQLException
|
||||
*/
|
||||
public function testMySQLEvaluateAllAtOnce()
|
||||
{
|
||||
// Expressions given as an OQL
|
||||
$aTests = array_values($this->VariousExpressionsProvider());
|
||||
|
||||
// Expressions given as a PHP statement
|
||||
foreach (array_values($this->NotYetParsableExpressionsProvider()) as $i => $aTest)
|
||||
{
|
||||
$sNewExpression = "return {$aTest[0]};";
|
||||
$oExpression = eval($sNewExpression);
|
||||
$sExpression = $oExpression->RenderExpression(true);
|
||||
$aTests[] = array($sExpression, $aTest[1]);
|
||||
}
|
||||
|
||||
$aExpressions = array();
|
||||
foreach ($aTests as $i => $aTest)
|
||||
{
|
||||
$aExpressions[] = "{$aTest[0]} as test_$i";
|
||||
}
|
||||
|
||||
$sSelects = implode(', ', $aExpressions);
|
||||
$sQuery = "SELECT $sSelects";
|
||||
|
||||
$this->debug($sQuery);
|
||||
$aResults = CMDBSource::QueryToArray($sQuery);
|
||||
|
||||
foreach ($aTests as $i => $aTest)
|
||||
{
|
||||
$value = $aResults[0]["test_$i"];
|
||||
$expectedValue = $aTest[1];
|
||||
$this->debug("Test #$i: {$aTests[$i][0]} => ".var_export($value, true));
|
||||
static::assertEquals($expectedValue, $value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers DBObject::EvaluateExpression
|
||||
* @dataProvider ExpressionsWithObjectFieldsProvider
|
||||
*
|
||||
* @param $sClass
|
||||
* @param $aValues
|
||||
* @param $sExpression
|
||||
* @param $expected
|
||||
*
|
||||
* @throws \CoreException
|
||||
* @throws \OQLException
|
||||
*/
|
||||
public function testExpressionsWithObjectFields($sClass, $aValues, $sExpression, $expected)
|
||||
{
|
||||
$oObject = MetaModel::NewObject($sClass, $aValues);
|
||||
$oExpression = Expression::FromOQL($sExpression);
|
||||
|
||||
$res = $oObject->EvaluateExpression($oExpression);
|
||||
|
||||
static::assertEquals($expected, $res);
|
||||
}
|
||||
|
||||
public function ExpressionsWithObjectFieldsProvider()
|
||||
{
|
||||
return array(
|
||||
array('Location', array('name' => 'Grenoble', 'org_id' => 2), 'org_id', 2),
|
||||
array('Location', array('name' => 'Grenoble', 'org_id' => 2), 'CONCAT(SUBSTR(name, 4), " cause")', 'noble cause'),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider ExpressionWithParametersProvider
|
||||
*
|
||||
* @param $sExpression
|
||||
* @param $aParameters
|
||||
* @param $expected
|
||||
*
|
||||
* @throws \OQLException
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function testExpressionWithParameters($sExpression, $aParameters, $expected)
|
||||
{
|
||||
$oExpression = Expression::FromOQL($sExpression);
|
||||
$res = $oExpression->Evaluate($aParameters);
|
||||
static::assertEquals($expected, $res);
|
||||
}
|
||||
|
||||
public function ExpressionWithParametersProvider()
|
||||
{
|
||||
return array(
|
||||
array('CONCAT(SUBSTR(name, 4), " cause")', array('name' => 'noble'), 'le cause'),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check Expression::IfTrue
|
||||
*
|
||||
* @covers Expression::FromOQL
|
||||
* @covers Expression::IsTrue
|
||||
* @dataProvider TrueExpressionsProvider
|
||||
*
|
||||
* @param $sExpression
|
||||
* @param $bExpectTrue
|
||||
*
|
||||
* @throws \OQLException
|
||||
*/
|
||||
public function testTrueExpressions($sExpression, $bExpectTrue)
|
||||
{
|
||||
$oExpression = Expression::FromOQL($sExpression);
|
||||
|
||||
$res = $oExpression->IsTrue();
|
||||
if ($bExpectTrue)
|
||||
{
|
||||
static::assertTrue($res, 'arg: '.$sExpression);
|
||||
}
|
||||
else
|
||||
{
|
||||
static::assertFalse($res, 'arg: '.$sExpression);
|
||||
}
|
||||
}
|
||||
|
||||
public function TrueExpressionsProvider()
|
||||
{
|
||||
$aExpressions = array(
|
||||
array('1', true),
|
||||
array('0 OR 0', false),
|
||||
array('1 AND 1', true),
|
||||
array('1 AND (1 OR 0)', true)
|
||||
);
|
||||
// Build a comprehensive index
|
||||
$aRet = array();
|
||||
foreach ($aExpressions as $aExp)
|
||||
{
|
||||
$aRet[$aExp[0]] = $aExp;
|
||||
}
|
||||
return $aRet;
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers FunctionExpression::Evaluate()
|
||||
* @dataProvider TimeFormatsProvider
|
||||
*
|
||||
* @param $sFormat
|
||||
* @param $bProcessed
|
||||
* @param $sValueOrException
|
||||
*
|
||||
* @throws \CoreException
|
||||
* @throws \MySQLException
|
||||
* @throws \MySQLQueryHasNoResultException
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function testTimeFormat($sFormat, $bProcessed, $sValueOrException)
|
||||
{
|
||||
$sDate = '2009-06-04 21:23:24';
|
||||
$oExpression = new FunctionExpression('DATE_FORMAT', array(new ScalarExpression($sDate), new ScalarExpression("%$sFormat")));
|
||||
if ($bProcessed)
|
||||
{
|
||||
$sqlValue = CMDBSource::QueryToScalar("SELECT DATE_FORMAT('$sDate', '%$sFormat')");
|
||||
static::assertEquals($sqlValue, $sValueOrException, 'Check test against MySQL');
|
||||
|
||||
$res = $oExpression->Evaluate(array());
|
||||
static::assertEquals($sValueOrException, $res, 'Check evaluation');
|
||||
}
|
||||
else
|
||||
{
|
||||
static::expectException($sValueOrException);
|
||||
$oExpression->Evaluate(array());
|
||||
}
|
||||
}
|
||||
|
||||
public function TimeFormatsProvider()
|
||||
{
|
||||
$aTests = array(
|
||||
array('a', true, 'Thu'),
|
||||
array('b', true, 'Jun'),
|
||||
array('c', true, '6'),
|
||||
array('D', true, '4th'),
|
||||
array('d', true, '04'),
|
||||
array('e', true, '4'),
|
||||
array('f', false, 'NotYetEvaluatedExpression'), // microseconds: no way!
|
||||
array('H', true, '21'),
|
||||
array('h', true, '09'),
|
||||
array('I', true, '09'),
|
||||
array('i', true, '23'),
|
||||
array('j', true, '155'), // day of the year
|
||||
array('k', true, '21'),
|
||||
array('l', true, '9'),
|
||||
array('M', true, 'June'),
|
||||
array('m', true, '06'),
|
||||
array('p', true, 'PM'),
|
||||
array('r', true, '09:23:24 PM'),
|
||||
array('S', true, '24'),
|
||||
array('s', true, '24'),
|
||||
array('T', true, '21:23:24'),
|
||||
array('U', false, 'NotYetEvaluatedExpression'), // Week sunday based (mode 0)
|
||||
array('u', false, 'NotYetEvaluatedExpression'), // Week monday based (mode 1)
|
||||
array('V', false, 'NotYetEvaluatedExpression'), // Week sunday based (mode 2)
|
||||
array('v', true, '23'), // Week monday based (mode 3 - ISO-8601)
|
||||
array('W', true, 'Thursday'),
|
||||
array('w', true, '4'),
|
||||
array('X', false, 'NotYetEvaluatedExpression'),
|
||||
array('x', true, '2009'), // to be used with %v (ISO - 8601)
|
||||
array('Y', true, '2009'),
|
||||
array('y', true, '09'),
|
||||
);
|
||||
$aRes = array();
|
||||
foreach ($aTests as $aTest)
|
||||
{
|
||||
$aRes["Format %{$aTest[0]}"] = $aTest;
|
||||
}
|
||||
return $aRes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Systematically check all supported format specs, for a given date
|
||||
*
|
||||
* @covers FunctionExpression::Evaluate()
|
||||
* @dataProvider EveryTimeFormatProvider
|
||||
*
|
||||
* @param $sDate
|
||||
*
|
||||
* @throws \CoreException
|
||||
* @throws \MySQLException
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function testEveryTimeFormat($sDate)
|
||||
{
|
||||
$aFormats = $this->TimeFormatsProvider();
|
||||
$aSelects = array();
|
||||
foreach ($aFormats as $sFormatDesc => $aFormatSpec)
|
||||
{
|
||||
$sFormat = $aFormatSpec[0];
|
||||
$bProcessed = $aFormatSpec[1];
|
||||
if ($bProcessed)
|
||||
{
|
||||
$aSelects["%$sFormat"] = "DATE_FORMAT('$sDate', '%$sFormat') AS `$sFormat`";
|
||||
}
|
||||
}
|
||||
$sSelects = "SELECT ".implode(', ', $aSelects);
|
||||
$aRes = CMDBSource::QueryToArray($sSelects);
|
||||
$aRow = $aRes[0];
|
||||
foreach ($aFormats as $sFormatDesc => $aFormatSpec)
|
||||
{
|
||||
$sFormat = $aFormatSpec[0];
|
||||
$bProcessed = $aFormatSpec[1];
|
||||
if ($bProcessed)
|
||||
{
|
||||
$oExpression = new FunctionExpression('DATE_FORMAT', array(new ScalarExpression($sDate), new ScalarExpression("%$sFormat")));
|
||||
$res = $oExpression->Evaluate(array());
|
||||
static::assertEquals($aRow[$sFormat], $res, "Format %$sFormat not matching MySQL for '$sDate'");
|
||||
}
|
||||
}
|
||||
}
|
||||
public function EveryTimeFormatProvider()
|
||||
{
|
||||
return array(
|
||||
array('1971-07-19 8:40:00'),
|
||||
array('1999-12-31 23:59:59'),
|
||||
array('2000-01-01 00:00:00'),
|
||||
array('2009-06-04 21:23:24'),
|
||||
array('2020-02-29 23:59:59'),
|
||||
array('2030-10-21 23:59:59'),
|
||||
array('2050-12-21 23:59:59'),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Systematically check all supported format specs, for a range of dates
|
||||
*
|
||||
* @covers FunctionExpression::Evaluate()
|
||||
* @dataProvider EveryTimeFormatOnDateRangeProvider
|
||||
*
|
||||
* @param $sStartDate
|
||||
* @param $sInterval
|
||||
* @param $iRepeat
|
||||
*
|
||||
* @throws \CoreException
|
||||
* @throws \MySQLException
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function testEveryTimeFormatOnDateRange($sStartDate, $sInterval, $iRepeat)
|
||||
{
|
||||
$oDate = new DateTime($sStartDate);
|
||||
for ($i = 0 ; $i < $iRepeat ; $i++)
|
||||
{
|
||||
$sDate = date_format($oDate, 'Y-m-d, H:i:s');
|
||||
$this->debug("Checking '$sDate'");
|
||||
$this->testEveryTimeFormat($sDate);
|
||||
$oDate->add(new DateInterval($sInterval));
|
||||
}
|
||||
}
|
||||
|
||||
public function EveryTimeFormatOnDateRangeProvider()
|
||||
{
|
||||
return array(
|
||||
'10 years, day by day' => array('2000-01-01', 'P1D', 365 * 10),
|
||||
'1 day, hour by hour' => array('2000-01-01 00:01:02', 'PT1H', 24),
|
||||
'1 hour, minute by minute' => array('2000-01-01 00:01:02', 'PT1M', 60),
|
||||
'1 minute, second by second' => array('2000-01-01 00:01:02', 'PT1S', 60),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -83,4 +83,100 @@ class MetaModelTest extends ItopDataTestCase
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers MetaModel::GetDependentAttributes()
|
||||
* @dataProvider GetDependentAttributesProvider
|
||||
*
|
||||
* @param string $sClass
|
||||
* @param string $sAttCode
|
||||
* @param array $aExpectedAttCodes
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function testGetDependentAttributes($sClass, $sAttCode, array $aExpectedAttCodes)
|
||||
{
|
||||
$aRes = MetaModel::GetDependentAttributes($sClass, $sAttCode);
|
||||
// The order doesn't matter
|
||||
sort($aRes);
|
||||
sort($aExpectedAttCodes);
|
||||
static::assertEquals($aExpectedAttCodes, $aRes);
|
||||
}
|
||||
|
||||
public function GetDependentAttributesProvider()
|
||||
{
|
||||
$aRawCases = array(
|
||||
array('Person', 'org_id', array('location_id', 'org_name', 'org_id_friendlyname', 'org_id_obsolescence_flag')),
|
||||
array('Person', 'name', array('friendlyname')),
|
||||
array('Person', 'status', array('obsolescence_flag')),
|
||||
);
|
||||
$aRet = array();
|
||||
foreach ($aRawCases as $i => $aData)
|
||||
{
|
||||
$aRet[$aData[0].'::'.$aData[1]] = $aData;
|
||||
}
|
||||
return $aRet;
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers MetaModel::GetPrerequisiteAttributes()
|
||||
* @dataProvider GetPrerequisiteAttributesProvider
|
||||
*
|
||||
* @param string $sClass
|
||||
* @param string $sAttCode
|
||||
* @param array $aExpectedAttCodes
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function testGetPrerequisiteAttributes($sClass, $sAttCode, array $aExpectedAttCodes)
|
||||
{
|
||||
$aRes = MetaModel::GetPrerequisiteAttributes($sClass, $sAttCode);
|
||||
// The order doesn't matter
|
||||
sort($aRes);
|
||||
sort($aExpectedAttCodes);
|
||||
static::assertEquals($aRes, $aExpectedAttCodes);
|
||||
}
|
||||
|
||||
public function GetPrerequisiteAttributesProvider()
|
||||
{
|
||||
$aRawCases = array(
|
||||
array('Person', 'friendlyname', array('name', 'first_name')),
|
||||
array('Person', 'obsolescence_flag', array('status')),
|
||||
array('Person', 'org_id_friendlyname', array('org_id')),
|
||||
array('Person', 'org_id', array()),
|
||||
array('Person', 'org_name', array('org_id')),
|
||||
);
|
||||
$aRet = array();
|
||||
foreach ($aRawCases as $i => $aData)
|
||||
{
|
||||
$aRet[$aData[0].'::'.$aData[1]] = $aData;
|
||||
}
|
||||
return $aRet;
|
||||
}
|
||||
|
||||
/**
|
||||
* To be removed as soon as the dependencies on external fields are obsoleted
|
||||
* @Group Integration
|
||||
*/
|
||||
public function testManualVersusAutomaticDependenciesOnExtKeys()
|
||||
{
|
||||
foreach (\MetaModel::GetClasses() as $sClass)
|
||||
{
|
||||
if (\MetaModel::IsAbstract($sClass)) continue;
|
||||
|
||||
foreach (\MetaModel::ListAttributeDefs($sClass) as $sAttCode => $oAttDef)
|
||||
{
|
||||
if (\MetaModel::GetAttributeOrigin($sClass, $sAttCode) != $sClass) continue;
|
||||
if (!$oAttDef instanceof \AttributeExternalKey) continue;
|
||||
|
||||
$aManual = $oAttDef->Get('depends_on');
|
||||
$aAuto = \MetaModel::GetPrerequisiteAttributes($sClass, $sAttCode);
|
||||
// The order doesn't matter
|
||||
sort($aAuto);
|
||||
sort($aManual);
|
||||
static::assertEquals($aManual, $aAuto, "Class: $sClass, Attribute: $sAttCode");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user