mirror of
https://github.com/Combodo/iTop.git
synced 2026-09-29 21:09:10 +02: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;
|
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()
|
public function GetMaximumComboLength()
|
||||||
{
|
{
|
||||||
return $this->GetOptional('max_combo_length', MetaModel::GetConfig()->Get('max_combo_length'));
|
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)
|
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()
|
public static function IsScalar()
|
||||||
@@ -12793,7 +12820,17 @@ class AttributeObsolescenceFlag extends AttributeBoolean
|
|||||||
|
|
||||||
public function GetPrerequisiteAttributes($sClass = null)
|
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()
|
public function IsDirectField()
|
||||||
|
|||||||
@@ -569,43 +569,41 @@ abstract class DBObject implements iDisplay
|
|||||||
$this->Reload();
|
$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)
|
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");
|
||||||
// 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");
|
|
||||||
}
|
|
||||||
|
|
||||||
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 */
|
/** @var \DBObject $value */
|
||||||
if ($oDef->IsExternalField() && ($oDef->GetKeyAttCode() == $sAttCode))
|
$this->m_aCurrValues[$sCode] = $value->Get($oDef->GetExtAttCode());
|
||||||
{
|
$this->m_aLoadedAtt[$sCode] = true;
|
||||||
/** @var \DBObject $value */
|
}
|
||||||
$this->m_aCurrValues[$sCode] = $value->Get($oDef->GetExtAttCode());
|
elseif (in_array($sAttCode, $oDef->GetPrerequisiteAttributes(get_class($this))))
|
||||||
$this->m_aLoadedAtt[$sCode] = true;
|
{
|
||||||
}
|
$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...
|
$this->m_aCurrValues[$sCode] = $this->GetDefaultValue($sCode);
|
||||||
// Invalidate the corresponding fields so that they get reloaded in case they are needed (See Get())
|
unset($this->m_aLoadedAtt[$sCode]);
|
||||||
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]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ($oAttDef->IsLinkSet() && ($value != null))
|
if ($oAttDef->IsLinkSet() && ($value != null))
|
||||||
@@ -787,20 +785,20 @@ abstract class DBObject implements iDisplay
|
|||||||
{
|
{
|
||||||
// Standard case... we have the information directly
|
// 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
|
// Lazy load (polymorphism): complete by reloading the entire object
|
||||||
// #@# non-scalar attributes.... handle that differently?
|
|
||||||
$oKPI = new ExecutionKPI();
|
$oKPI = new ExecutionKPI();
|
||||||
$this->Reload();
|
$this->Reload();
|
||||||
$oKPI->ComputeStats('Reload', get_class($this).'/'.$sAttCode);
|
$oKPI->ComputeStats('Reload', get_class($this).'/'.$sAttCode);
|
||||||
}
|
}
|
||||||
elseif ($sAttCode == 'friendlyname')
|
elseif ($oAttDef->IsBasedOnOQLExpression())
|
||||||
{
|
{
|
||||||
// The friendly name is not computed and the object is dirty
|
// Recompute -which is likely to call Get()
|
||||||
// Todo: implement the computation of the friendly name based on sprintf()
|
//
|
||||||
//
|
/** @var AttributeFriendlyName|\AttributeObsolescenceFlag $oAttDef */
|
||||||
$this->m_aCurrValues[$sAttCode] = '';
|
$this->m_aCurrValues[$sAttCode] = $this->EvaluateExpression($oAttDef->GetOQLExpression());
|
||||||
|
$this->m_aLoadedAtt[$sAttCode] = true;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -5358,5 +5356,33 @@ abstract class DBObject implements iDisplay
|
|||||||
break;
|
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);
|
self::_check_subclass($sClass);
|
||||||
$oAtt = self::GetAttributeDef($sClass, $sAttCode);
|
$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();
|
return $oAtt->GetPrerequisiteAttributes();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,9 @@ class MissingQueryArgument extends CoreException
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class NotYetEvaluatedExpression extends CoreException
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @method Check($oModelReflection, array $aAliases, $sSourceQuery)
|
* @method Check($oModelReflection, array $aAliases, $sSourceQuery)
|
||||||
@@ -109,6 +112,47 @@ abstract class Expression
|
|||||||
*/
|
*/
|
||||||
abstract public function RenderExpression($bForSQL = false, &$aArgs = null, $bRetrofitParams = false);
|
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
|
* 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;
|
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
|
// recursive rendering
|
||||||
public function toJSON(&$aArgs = null, $bRetrofitParams = false)
|
public function toJSON(&$aArgs = null, $bRetrofitParams = false)
|
||||||
{
|
{
|
||||||
@@ -466,6 +520,149 @@ class BinaryExpression extends Expression
|
|||||||
return "($sLeft $sOperator $sRight)";
|
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}
|
* {@inheritDoc}
|
||||||
* @throws \MissingQueryArgument
|
* @throws \MissingQueryArgument
|
||||||
@@ -865,6 +1062,16 @@ class MatchExpression extends BinaryExpression
|
|||||||
return $sRet;
|
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)
|
public function Translate($aTranslationData, $bMatchAll = true, $bMarkFieldsAsResolved = true)
|
||||||
{
|
{
|
||||||
/** @var \FieldExpression $oLeft */
|
/** @var \FieldExpression $oLeft */
|
||||||
@@ -903,6 +1110,16 @@ class UnaryExpression extends Expression
|
|||||||
return CMDBSource::Quote($this->m_value);
|
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}
|
* {@inheritDoc}
|
||||||
* @throws \MissingQueryArgument
|
* @throws \MissingQueryArgument
|
||||||
@@ -1049,6 +1266,16 @@ class ScalarExpression extends UnaryExpression
|
|||||||
return $sRet;
|
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}
|
* {@inheritDoc}
|
||||||
* @see Expression::ToJSON()
|
* @see Expression::ToJSON()
|
||||||
@@ -1357,6 +1584,21 @@ class FieldExpression extends UnaryExpression
|
|||||||
return "`{$this->m_sParent}`.`{$this->m_sName}`";
|
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}
|
* {@inheritDoc}
|
||||||
* @see Expression::ToJSON()
|
* @see Expression::ToJSON()
|
||||||
@@ -1412,7 +1654,8 @@ class FieldExpression extends UnaryExpression
|
|||||||
|
|
||||||
public function ListRequiredFields()
|
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)
|
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}
|
* {@inheritDoc}
|
||||||
* @see Expression::ToJSON()
|
* @see Expression::ToJSON()
|
||||||
@@ -1933,6 +2186,16 @@ class ListExpression extends Expression
|
|||||||
return '('.implode(', ', $aRes).')';
|
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}
|
* {@inheritDoc}
|
||||||
* @see Expression::ToJSON()
|
* @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)
|
public function Browse(Closure $callback)
|
||||||
{
|
{
|
||||||
$callback($this);
|
$callback($this);
|
||||||
@@ -2240,6 +2513,252 @@ class FunctionExpression extends Expression
|
|||||||
return $this->m_sVerb.'('.implode(', ', $aRes).')';
|
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}
|
* {@inheritDoc}
|
||||||
* @see Expression::ToJSON()
|
* @see Expression::ToJSON()
|
||||||
@@ -2566,6 +3085,17 @@ class IntervalExpression extends Expression
|
|||||||
return 'INTERVAL '.$this->m_oValue->RenderExpression($bForSQL, $aArgs, $bRetrofitParams).' '.$this->m_sUnit;
|
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}
|
* {@inheritDoc}
|
||||||
* @see Expression::ToJSON()
|
* @see Expression::ToJSON()
|
||||||
@@ -2684,6 +3214,21 @@ class CharConcatExpression extends Expression
|
|||||||
return "CAST(CONCAT(".implode(', ', $aRes).") AS CHAR)";
|
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}
|
* {@inheritDoc}
|
||||||
* @see Expression::ToJSON()
|
* @see Expression::ToJSON()
|
||||||
@@ -2825,6 +3370,21 @@ class CharConcatWSExpression extends CharConcatExpression
|
|||||||
return "CAST(CONCAT_WS($sSep, ".implode(', ', $aRes).") AS CHAR)";
|
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)
|
public function Browse(Closure $callback)
|
||||||
{
|
{
|
||||||
$callback($this);
|
$callback($this);
|
||||||
|
|||||||
@@ -610,9 +610,6 @@
|
|||||||
</field>
|
</field>
|
||||||
<field id="manager_id" xsi:type="AttributeExternalKey">
|
<field id="manager_id" xsi:type="AttributeExternalKey">
|
||||||
<filter><![CDATA[SELECT Person]]></filter>
|
<filter><![CDATA[SELECT Person]]></filter>
|
||||||
<dependencies>
|
|
||||||
<attribute id="org_id"/>
|
|
||||||
</dependencies>
|
|
||||||
<sql>manager_id</sql>
|
<sql>manager_id</sql>
|
||||||
<target_class>Person</target_class>
|
<target_class>Person</target_class>
|
||||||
<is_null_allowed>true</is_null_allowed>
|
<is_null_allowed>true</is_null_allowed>
|
||||||
|
|||||||
@@ -713,7 +713,7 @@ class ItopDataTestCase extends ItopTestCase
|
|||||||
$iId = $oLnk->Get('functionalci_id');
|
$iId = $oLnk->Get('functionalci_id');
|
||||||
if (!empty($aWaitedCIList))
|
if (!empty($aWaitedCIList))
|
||||||
{
|
{
|
||||||
$this->assertTrue(array_key_exists($iId, $aWaitedCIList));
|
$this->assertArrayHasKey($iId, $aWaitedCIList);
|
||||||
$this->assertEquals($aWaitedCIList[$iId], $oLnk->Get('impact_code'));
|
$this->assertEquals($aWaitedCIList[$iId], $oLnk->Get('impact_code'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -737,7 +737,7 @@ class ItopDataTestCase extends ItopTestCase
|
|||||||
$iId = $oLnk->Get('contact_id');
|
$iId = $oLnk->Get('contact_id');
|
||||||
if (!empty($aWaitedContactList))
|
if (!empty($aWaitedContactList))
|
||||||
{
|
{
|
||||||
$this->assertTrue(array_key_exists($iId, $aWaitedContactList));
|
$this->assertArrayHasKey($iId, $aWaitedContactList);
|
||||||
foreach ($aWaitedContactList[$iId] as $sAttCode => $oValue)
|
foreach ($aWaitedContactList[$iId] as $sAttCode => $oValue)
|
||||||
{
|
{
|
||||||
if (MetaModel::IsValidAttCode(get_class($oTicket), $sAttCode))
|
if (MetaModel::IsValidAttCode(get_class($oTicket), $sAttCode))
|
||||||
@@ -756,5 +756,29 @@ class ItopDataTestCase extends ItopTestCase
|
|||||||
$this->iTestOrgId = $oOrg->GetKey();
|
$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
|
* Test default page name
|
||||||
|
* @covers DBObject::GetUIPage
|
||||||
*/
|
*/
|
||||||
public function testGetUIPage()
|
public function testGetUIPage()
|
||||||
{
|
{
|
||||||
@@ -81,6 +82,9 @@ class DBObjectTest extends ItopDataTestCase
|
|||||||
array('PHP_INT_MIN', false));
|
array('PHP_INT_MIN', false));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers DBObject::GetOriginal
|
||||||
|
*/
|
||||||
public function testGetOriginal()
|
public function testGetOriginal()
|
||||||
{
|
{
|
||||||
$oObject = $this->CreateUserRequest(190664);
|
$oObject = $this->CreateUserRequest(190664);
|
||||||
@@ -88,4 +92,114 @@ class DBObjectTest extends ItopDataTestCase
|
|||||||
static::assertNull($oObject->GetOriginal('sla_tto_passed'));
|
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