mirror of
https://github.com/Combodo/iTop.git
synced 2026-02-13 15:34:12 +01:00
Compare commits
10 Commits
3.0.0-beta
...
feature/b1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e508f645f3 | ||
|
|
81f7f9d70b | ||
|
|
59c9a98b87 | ||
|
|
d57fb3e24e | ||
|
|
88ae239a56 | ||
|
|
30494756fc | ||
|
|
1900546440 | ||
|
|
d8dd0437b3 | ||
|
|
d1d50e0448 | ||
|
|
1c87c15222 |
@@ -1133,7 +1133,108 @@ class DBObjectSearch extends DBSearch
|
||||
{
|
||||
return $this->m_oSearchCondition->ApplyParameters(array_merge($this->m_aParams, $aArgs));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @todo: check if the clone is mandatory or optional. (the performance would be better without cloning)
|
||||
*
|
||||
* @param bool $bClone
|
||||
*
|
||||
* @return DBObjectSearch|DBSearch
|
||||
*/
|
||||
public function ShorthandExpansion($bClone = false)
|
||||
{
|
||||
if ($bClone)
|
||||
{
|
||||
$oDbObject = $this->DeepClone();
|
||||
}
|
||||
else
|
||||
{
|
||||
$oDbObject = $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* This callback add joins based on the classes found inside ExternalFieldExpression::$aFields
|
||||
* to do so, it is applied on each Expression of $this->m_oSearchCondition.
|
||||
*
|
||||
* @param $oExpression
|
||||
*/
|
||||
$callback = function ($oExpression) use ($oDbObject)
|
||||
{
|
||||
if (!$oExpression instanceof ExternalFieldExpression) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
/** @var FieldExpression[] $aFieldExpressionsPointingTo */
|
||||
$aFields = $oExpression->GetFields();
|
||||
|
||||
$aRealiasingMap = array();
|
||||
$aExpressionNewConditions = array();
|
||||
foreach ($aFields as $aFieldExpressionPointingTo)
|
||||
{
|
||||
$oFilter = new DBObjectSearch($aFieldExpressionPointingTo['sClass']);
|
||||
$aExpressionNewConditions[] = array(
|
||||
'oFilter' => $oFilter,
|
||||
'sExtKeyAttCode' => $aFieldExpressionPointingTo['sAttCode'],
|
||||
);
|
||||
|
||||
$aRealiasingMap[$aFieldExpressionPointingTo['sClass']] = $aFieldExpressionPointingTo['sAlias'];
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* the iteration below is weird because wee need to
|
||||
* - iterate in the reverse order
|
||||
* - the iteration access the "index+1" so wee start at "length-1" (which is "count()-2")
|
||||
* - whe stop at the index 1, because the index 0 is merged into the $oDbObject
|
||||
*/
|
||||
for ($i = count($aExpressionNewConditions) - 2; $i > 0; $i--)
|
||||
{
|
||||
$aExpressionNewConditions[$i]['oFilter']->AddCondition_PointingTo(
|
||||
$aExpressionNewConditions[$i+1]['oFilter'],
|
||||
$aExpressionNewConditions[$i]['sExtKeyAttCode'],
|
||||
TREE_OPERATOR_EQUALS,
|
||||
$aRealiasingMap
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
$oDbObject->AddCondition_PointingTo(
|
||||
$aExpressionNewConditions[1]['oFilter'],
|
||||
$aExpressionNewConditions[0]['sExtKeyAttCode'],
|
||||
TREE_OPERATOR_EQUALS,
|
||||
$aRealiasingMap
|
||||
);
|
||||
|
||||
foreach ($aRealiasingMap as $sOldAlias => $sNewAlias)
|
||||
{
|
||||
if ($sOldAlias == $sNewAlias)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($sOldAlias != $aFields['sAlias'])
|
||||
{
|
||||
continue;
|
||||
}
|
||||
$aFields['sAlias'] = $sNewAlias;
|
||||
}
|
||||
|
||||
$oExpression->SetFields($aFields);
|
||||
}; //end of the callback definition
|
||||
|
||||
|
||||
//Add the joins
|
||||
$this->m_oSearchCondition->Browse($callback);
|
||||
|
||||
//replace the ExternalFieldExpression by a FieldExpression (based on last ExternalFieldExpression::$aFields)
|
||||
$this->m_oSearchCondition->Translate(array(), false, false);//TODO: check if this call is correct
|
||||
|
||||
return $oDbObject;
|
||||
}
|
||||
|
||||
public function ToOQL($bDevelopParams = false, $aContextParams = null, $bWithAllowAllFlag = false)
|
||||
{
|
||||
// Currently unused, but could be useful later
|
||||
@@ -1262,6 +1363,23 @@ class DBObjectSearch extends DBSearch
|
||||
$oRight = $this->OQLExpressionToCondition($sQuery, $oExpression->GetRightExpr(), $aClassAliases);
|
||||
return new BinaryExpression($oLeft, $sOperator, $oRight);
|
||||
}
|
||||
elseif ($oExpression instanceof ExternalFieldOqlExpression)
|
||||
{
|
||||
//TODO : convert FieldOqlExpression[] to FieldExpression[]
|
||||
$aOqlFieldExpression = $oExpression->GetExpressions();
|
||||
$aFields = array();
|
||||
|
||||
foreach ($aOqlFieldExpression as $oOqlFieldExpression)
|
||||
{
|
||||
$aFields[] = array(
|
||||
'sClass' => $oOqlFieldExpression->GetParent(),
|
||||
'sAlias' => $oOqlFieldExpression->GetParent(),
|
||||
'sAttCode' => $oOqlFieldExpression->GetName(),
|
||||
);
|
||||
}
|
||||
|
||||
return new ExternalFieldExpression($oExpression->GetName(), $aFields);
|
||||
}
|
||||
elseif ($oExpression instanceof FieldOqlExpression)
|
||||
{
|
||||
$sClassAlias = $oExpression->GetParent();
|
||||
@@ -1530,7 +1648,7 @@ class DBObjectSearch extends DBSearch
|
||||
$aContextData['sRequestUri'] = '';
|
||||
}
|
||||
|
||||
// Need to identify the query
|
||||
// Need to identify the querySELECT `Contact` FROM Contact AS `Contact` JOIN Organization AS `Organization` ON `Contact`.org_id = `Organization`.id JOIN DeliveryModel AS `DeliveryModel` ON `Organization`.deliverymodel_id = `DeliveryModel`.id WHERE ((((Contact.org_id->deliverymodel_id->name = 'Standard support') AND 1) AND 1) AND 1)
|
||||
$sOqlQuery = $oSearch->ToOql(false, null, true);
|
||||
if ((strpos($sOqlQuery, '`id` IN (') !== false) || (strpos($sOqlQuery, '`id` NOT IN (') !== false))
|
||||
{
|
||||
@@ -1636,7 +1754,8 @@ class DBObjectSearch extends DBSearch
|
||||
|
||||
if (!isset($oSQLQuery))
|
||||
{
|
||||
$oKPI = new ExecutionKPI();
|
||||
$oSearch = $oSearch->ShorthandExpansion(true);//TODO : check if the clone is really needed (1st param of ShorthandExpansion)
|
||||
$oKPI = new ExecutionKPI();
|
||||
$oSQLQuery = $oSearch->BuildSQLQueryStruct($aAttToLoad, $bGetCount, $aModifierProperties, $aGroupByExpr, $aSelectedClasses, $aSelectExpr);
|
||||
$oKPI->ComputeStats('BuildSQLQueryStruct', $sOqlQuery);
|
||||
|
||||
|
||||
@@ -841,6 +841,74 @@ class FalseExpression extends ScalarExpression
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Class ExternalFieldExpression
|
||||
*
|
||||
* @todo verify if all required methods are implemented (ie: at first I missed the `Render` method, so DBObjectSearch::toOQL() result was wrong)
|
||||
*
|
||||
*/
|
||||
class ExternalFieldExpression extends UnaryExpression
|
||||
{
|
||||
/**
|
||||
* @var array[] array containing the shorthand chained fields & their classes
|
||||
* ['sClass'] string the Class
|
||||
* ['sAlias'] string the Class alias
|
||||
* ['sAttCode'] string the attribute code
|
||||
*/
|
||||
protected $m_aFields = array();
|
||||
protected $m_sName;
|
||||
|
||||
public function __construct($sName, $aFields)
|
||||
{
|
||||
parent::__construct($sName);
|
||||
|
||||
$this->SetFields($aFields);
|
||||
}
|
||||
|
||||
public function SetFields($aFields)
|
||||
{
|
||||
$this->m_aFields = $aFields;
|
||||
}
|
||||
|
||||
public function GetFields()
|
||||
{
|
||||
return $this->m_aFields;
|
||||
}
|
||||
|
||||
/**
|
||||
* used by DBObjectSearch::ShorthandExpansion(). it result in the ExternalFieldExpression being replaced by a FieldExpression mathching the final entry in self::$m_aFields
|
||||
*
|
||||
* @param array $aTranslationData
|
||||
* @param bool $bMatchAll
|
||||
* @param bool $bMarkFieldsAsResolved
|
||||
*
|
||||
* @return Expression|FieldExpression|UnaryExpression
|
||||
*/
|
||||
public function Translate($aTranslationData, $bMatchAll = true, $bMarkFieldsAsResolved = true)
|
||||
{
|
||||
$aFields = $this->GetFields();
|
||||
$aLastField = end($aFields);
|
||||
|
||||
$oRet = new FieldExpression($aLastField['sAttCode'], $aLastField['sAlias']);
|
||||
|
||||
return $oRet;
|
||||
}
|
||||
|
||||
public function Render(&$aArgs = null, $bRetrofitParams = false)
|
||||
{
|
||||
$aAttCode = array();
|
||||
|
||||
$aFields = $this->GetFields();
|
||||
foreach ($aFields as $field) {
|
||||
$aAttCode[] = $field['sAttCode'];
|
||||
}
|
||||
|
||||
return $aFields[0]['sAlias'] . '.' . implode('->', $aAttCode);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class FieldExpression extends UnaryExpression
|
||||
{
|
||||
protected $m_sParent;
|
||||
|
||||
@@ -115,6 +115,8 @@ class OQLLexerRaw
|
||||
'/\GWHERE/ ',
|
||||
'/\GJOIN/ ',
|
||||
'/\GON/ ',
|
||||
'/\G->/ ',
|
||||
'/\G:/ ',
|
||||
'/\G\// ',
|
||||
'/\G\\*/ ',
|
||||
'/\G\\+/ ',
|
||||
@@ -316,329 +318,339 @@ class OQLLexerRaw
|
||||
function yy_r1_8($yy_subpatterns)
|
||||
{
|
||||
|
||||
$this->token = OQLParser::MATH_DIV;
|
||||
$this->token = OQLParser::ARROW;
|
||||
}
|
||||
function yy_r1_9($yy_subpatterns)
|
||||
{
|
||||
|
||||
$this->token = OQLParser::MATH_MULT;
|
||||
$this->token = OQLParser::COLON;
|
||||
}
|
||||
function yy_r1_10($yy_subpatterns)
|
||||
{
|
||||
|
||||
$this->token = OQLParser::MATH_PLUS;
|
||||
$this->token = OQLParser::MATH_DIV;
|
||||
}
|
||||
function yy_r1_11($yy_subpatterns)
|
||||
{
|
||||
|
||||
$this->token = OQLParser::MATH_MINUS;
|
||||
$this->token = OQLParser::MATH_MULT;
|
||||
}
|
||||
function yy_r1_12($yy_subpatterns)
|
||||
{
|
||||
|
||||
$this->token = OQLParser::LOG_AND;
|
||||
$this->token = OQLParser::MATH_PLUS;
|
||||
}
|
||||
function yy_r1_13($yy_subpatterns)
|
||||
{
|
||||
|
||||
$this->token = OQLParser::LOG_OR;
|
||||
$this->token = OQLParser::MATH_MINUS;
|
||||
}
|
||||
function yy_r1_14($yy_subpatterns)
|
||||
{
|
||||
|
||||
$this->token = OQLParser::BITWISE_OR;
|
||||
$this->token = OQLParser::LOG_AND;
|
||||
}
|
||||
function yy_r1_15($yy_subpatterns)
|
||||
{
|
||||
|
||||
$this->token = OQLParser::BITWISE_AND;
|
||||
$this->token = OQLParser::LOG_OR;
|
||||
}
|
||||
function yy_r1_16($yy_subpatterns)
|
||||
{
|
||||
|
||||
$this->token = OQLParser::BITWISE_XOR;
|
||||
$this->token = OQLParser::BITWISE_OR;
|
||||
}
|
||||
function yy_r1_17($yy_subpatterns)
|
||||
{
|
||||
|
||||
$this->token = OQLParser::BITWISE_LEFT_SHIFT;
|
||||
$this->token = OQLParser::BITWISE_AND;
|
||||
}
|
||||
function yy_r1_18($yy_subpatterns)
|
||||
{
|
||||
|
||||
$this->token = OQLParser::BITWISE_RIGHT_SHIFT;
|
||||
$this->token = OQLParser::BITWISE_XOR;
|
||||
}
|
||||
function yy_r1_19($yy_subpatterns)
|
||||
{
|
||||
|
||||
$this->token = OQLParser::COMA;
|
||||
$this->token = OQLParser::BITWISE_LEFT_SHIFT;
|
||||
}
|
||||
function yy_r1_20($yy_subpatterns)
|
||||
{
|
||||
|
||||
$this->token = OQLParser::PAR_OPEN;
|
||||
$this->token = OQLParser::BITWISE_RIGHT_SHIFT;
|
||||
}
|
||||
function yy_r1_21($yy_subpatterns)
|
||||
{
|
||||
|
||||
$this->token = OQLParser::PAR_CLOSE;
|
||||
$this->token = OQLParser::COMA;
|
||||
}
|
||||
function yy_r1_22($yy_subpatterns)
|
||||
{
|
||||
|
||||
$this->token = OQLParser::REGEXP;
|
||||
$this->token = OQLParser::PAR_OPEN;
|
||||
}
|
||||
function yy_r1_23($yy_subpatterns)
|
||||
{
|
||||
|
||||
$this->token = OQLParser::EQ;
|
||||
$this->token = OQLParser::PAR_CLOSE;
|
||||
}
|
||||
function yy_r1_24($yy_subpatterns)
|
||||
{
|
||||
|
||||
$this->token = OQLParser::NOT_EQ;
|
||||
$this->token = OQLParser::REGEXP;
|
||||
}
|
||||
function yy_r1_25($yy_subpatterns)
|
||||
{
|
||||
|
||||
$this->token = OQLParser::GT;
|
||||
$this->token = OQLParser::EQ;
|
||||
}
|
||||
function yy_r1_26($yy_subpatterns)
|
||||
{
|
||||
|
||||
$this->token = OQLParser::LT;
|
||||
$this->token = OQLParser::NOT_EQ;
|
||||
}
|
||||
function yy_r1_27($yy_subpatterns)
|
||||
{
|
||||
|
||||
$this->token = OQLParser::GE;
|
||||
$this->token = OQLParser::GT;
|
||||
}
|
||||
function yy_r1_28($yy_subpatterns)
|
||||
{
|
||||
|
||||
$this->token = OQLParser::LE;
|
||||
$this->token = OQLParser::LT;
|
||||
}
|
||||
function yy_r1_29($yy_subpatterns)
|
||||
{
|
||||
|
||||
$this->token = OQLParser::LIKE;
|
||||
$this->token = OQLParser::GE;
|
||||
}
|
||||
function yy_r1_30($yy_subpatterns)
|
||||
{
|
||||
|
||||
$this->token = OQLParser::NOT_LIKE;
|
||||
$this->token = OQLParser::LE;
|
||||
}
|
||||
function yy_r1_31($yy_subpatterns)
|
||||
{
|
||||
|
||||
$this->token = OQLParser::IN;
|
||||
$this->token = OQLParser::LIKE;
|
||||
}
|
||||
function yy_r1_32($yy_subpatterns)
|
||||
{
|
||||
|
||||
$this->token = OQLParser::NOT_IN;
|
||||
$this->token = OQLParser::NOT_LIKE;
|
||||
}
|
||||
function yy_r1_33($yy_subpatterns)
|
||||
{
|
||||
|
||||
$this->token = OQLParser::INTERVAL;
|
||||
$this->token = OQLParser::IN;
|
||||
}
|
||||
function yy_r1_34($yy_subpatterns)
|
||||
{
|
||||
|
||||
$this->token = OQLParser::F_IF;
|
||||
$this->token = OQLParser::NOT_IN;
|
||||
}
|
||||
function yy_r1_35($yy_subpatterns)
|
||||
{
|
||||
|
||||
$this->token = OQLParser::F_ELT;
|
||||
$this->token = OQLParser::INTERVAL;
|
||||
}
|
||||
function yy_r1_36($yy_subpatterns)
|
||||
{
|
||||
|
||||
$this->token = OQLParser::F_COALESCE;
|
||||
$this->token = OQLParser::F_IF;
|
||||
}
|
||||
function yy_r1_37($yy_subpatterns)
|
||||
{
|
||||
|
||||
$this->token = OQLParser::F_ISNULL;
|
||||
$this->token = OQLParser::F_ELT;
|
||||
}
|
||||
function yy_r1_38($yy_subpatterns)
|
||||
{
|
||||
|
||||
$this->token = OQLParser::F_CONCAT;
|
||||
$this->token = OQLParser::F_COALESCE;
|
||||
}
|
||||
function yy_r1_39($yy_subpatterns)
|
||||
{
|
||||
|
||||
$this->token = OQLParser::F_SUBSTR;
|
||||
$this->token = OQLParser::F_ISNULL;
|
||||
}
|
||||
function yy_r1_40($yy_subpatterns)
|
||||
{
|
||||
|
||||
$this->token = OQLParser::F_TRIM;
|
||||
$this->token = OQLParser::F_CONCAT;
|
||||
}
|
||||
function yy_r1_41($yy_subpatterns)
|
||||
{
|
||||
|
||||
$this->token = OQLParser::F_DATE;
|
||||
$this->token = OQLParser::F_SUBSTR;
|
||||
}
|
||||
function yy_r1_42($yy_subpatterns)
|
||||
{
|
||||
|
||||
$this->token = OQLParser::F_DATE_FORMAT;
|
||||
$this->token = OQLParser::F_TRIM;
|
||||
}
|
||||
function yy_r1_43($yy_subpatterns)
|
||||
{
|
||||
|
||||
$this->token = OQLParser::F_CURRENT_DATE;
|
||||
$this->token = OQLParser::F_DATE;
|
||||
}
|
||||
function yy_r1_44($yy_subpatterns)
|
||||
{
|
||||
|
||||
$this->token = OQLParser::F_NOW;
|
||||
$this->token = OQLParser::F_DATE_FORMAT;
|
||||
}
|
||||
function yy_r1_45($yy_subpatterns)
|
||||
{
|
||||
|
||||
$this->token = OQLParser::F_TIME;
|
||||
$this->token = OQLParser::F_CURRENT_DATE;
|
||||
}
|
||||
function yy_r1_46($yy_subpatterns)
|
||||
{
|
||||
|
||||
$this->token = OQLParser::F_TO_DAYS;
|
||||
$this->token = OQLParser::F_NOW;
|
||||
}
|
||||
function yy_r1_47($yy_subpatterns)
|
||||
{
|
||||
|
||||
$this->token = OQLParser::F_FROM_DAYS;
|
||||
$this->token = OQLParser::F_TIME;
|
||||
}
|
||||
function yy_r1_48($yy_subpatterns)
|
||||
{
|
||||
|
||||
$this->token = OQLParser::F_YEAR;
|
||||
$this->token = OQLParser::F_TO_DAYS;
|
||||
}
|
||||
function yy_r1_49($yy_subpatterns)
|
||||
{
|
||||
|
||||
$this->token = OQLParser::F_MONTH;
|
||||
$this->token = OQLParser::F_FROM_DAYS;
|
||||
}
|
||||
function yy_r1_50($yy_subpatterns)
|
||||
{
|
||||
|
||||
$this->token = OQLParser::F_DAY;
|
||||
$this->token = OQLParser::F_YEAR;
|
||||
}
|
||||
function yy_r1_51($yy_subpatterns)
|
||||
{
|
||||
|
||||
$this->token = OQLParser::F_HOUR;
|
||||
$this->token = OQLParser::F_MONTH;
|
||||
}
|
||||
function yy_r1_52($yy_subpatterns)
|
||||
{
|
||||
|
||||
$this->token = OQLParser::F_MINUTE;
|
||||
$this->token = OQLParser::F_DAY;
|
||||
}
|
||||
function yy_r1_53($yy_subpatterns)
|
||||
{
|
||||
|
||||
$this->token = OQLParser::F_SECOND;
|
||||
$this->token = OQLParser::F_HOUR;
|
||||
}
|
||||
function yy_r1_54($yy_subpatterns)
|
||||
{
|
||||
|
||||
$this->token = OQLParser::F_DATE_ADD;
|
||||
$this->token = OQLParser::F_MINUTE;
|
||||
}
|
||||
function yy_r1_55($yy_subpatterns)
|
||||
{
|
||||
|
||||
$this->token = OQLParser::F_DATE_SUB;
|
||||
$this->token = OQLParser::F_SECOND;
|
||||
}
|
||||
function yy_r1_56($yy_subpatterns)
|
||||
{
|
||||
|
||||
$this->token = OQLParser::F_ROUND;
|
||||
$this->token = OQLParser::F_DATE_ADD;
|
||||
}
|
||||
function yy_r1_57($yy_subpatterns)
|
||||
{
|
||||
|
||||
$this->token = OQLParser::F_FLOOR;
|
||||
$this->token = OQLParser::F_DATE_SUB;
|
||||
}
|
||||
function yy_r1_58($yy_subpatterns)
|
||||
{
|
||||
|
||||
$this->token = OQLParser::F_INET_ATON;
|
||||
$this->token = OQLParser::F_ROUND;
|
||||
}
|
||||
function yy_r1_59($yy_subpatterns)
|
||||
{
|
||||
|
||||
$this->token = OQLParser::F_INET_NTOA;
|
||||
$this->token = OQLParser::F_FLOOR;
|
||||
}
|
||||
function yy_r1_60($yy_subpatterns)
|
||||
{
|
||||
|
||||
$this->token = OQLParser::BELOW;
|
||||
$this->token = OQLParser::F_INET_ATON;
|
||||
}
|
||||
function yy_r1_61($yy_subpatterns)
|
||||
{
|
||||
|
||||
$this->token = OQLParser::BELOW_STRICT;
|
||||
$this->token = OQLParser::F_INET_NTOA;
|
||||
}
|
||||
function yy_r1_62($yy_subpatterns)
|
||||
{
|
||||
|
||||
$this->token = OQLParser::NOT_BELOW;
|
||||
$this->token = OQLParser::BELOW;
|
||||
}
|
||||
function yy_r1_63($yy_subpatterns)
|
||||
{
|
||||
|
||||
$this->token = OQLParser::NOT_BELOW_STRICT;
|
||||
$this->token = OQLParser::BELOW_STRICT;
|
||||
}
|
||||
function yy_r1_64($yy_subpatterns)
|
||||
{
|
||||
|
||||
$this->token = OQLParser::ABOVE;
|
||||
$this->token = OQLParser::NOT_BELOW;
|
||||
}
|
||||
function yy_r1_65($yy_subpatterns)
|
||||
{
|
||||
|
||||
$this->token = OQLParser::ABOVE_STRICT;
|
||||
$this->token = OQLParser::NOT_BELOW_STRICT;
|
||||
}
|
||||
function yy_r1_66($yy_subpatterns)
|
||||
{
|
||||
|
||||
$this->token = OQLParser::NOT_ABOVE;
|
||||
$this->token = OQLParser::ABOVE;
|
||||
}
|
||||
function yy_r1_67($yy_subpatterns)
|
||||
{
|
||||
|
||||
$this->token = OQLParser::NOT_ABOVE_STRICT;
|
||||
$this->token = OQLParser::ABOVE_STRICT;
|
||||
}
|
||||
function yy_r1_68($yy_subpatterns)
|
||||
{
|
||||
|
||||
$this->token = OQLParser::HEXVAL;
|
||||
$this->token = OQLParser::NOT_ABOVE;
|
||||
}
|
||||
function yy_r1_69($yy_subpatterns)
|
||||
{
|
||||
|
||||
$this->token = OQLParser::NUMVAL;
|
||||
$this->token = OQLParser::NOT_ABOVE_STRICT;
|
||||
}
|
||||
function yy_r1_70($yy_subpatterns)
|
||||
{
|
||||
|
||||
$this->token = OQLParser::STRVAL;
|
||||
$this->token = OQLParser::HEXVAL;
|
||||
}
|
||||
function yy_r1_71($yy_subpatterns)
|
||||
{
|
||||
|
||||
$this->token = OQLParser::NAME;
|
||||
$this->token = OQLParser::NUMVAL;
|
||||
}
|
||||
function yy_r1_72($yy_subpatterns)
|
||||
{
|
||||
|
||||
$this->token = OQLParser::VARNAME;
|
||||
$this->token = OQLParser::STRVAL;
|
||||
}
|
||||
function yy_r1_73($yy_subpatterns)
|
||||
{
|
||||
|
||||
$this->token = OQLParser::NAME;
|
||||
}
|
||||
function yy_r1_74($yy_subpatterns)
|
||||
{
|
||||
|
||||
$this->token = OQLParser::VARNAME;
|
||||
}
|
||||
function yy_r1_75($yy_subpatterns)
|
||||
{
|
||||
|
||||
$this->token = OQLParser::DOT;
|
||||
|
||||
@@ -88,6 +88,8 @@ where = "WHERE"
|
||||
join = "JOIN"
|
||||
on = "ON"
|
||||
coma = ","
|
||||
arrow = "->"
|
||||
colon = ":"
|
||||
par_open = "("
|
||||
par_close = ")"
|
||||
math_div = "/"
|
||||
@@ -170,7 +172,7 @@ numval = /([0-9]+)/
|
||||
strval = /"([^\\"]|\\"|\\\\)*"|'.chr(94).chr(39).'([^\\'.chr(39).']|\\'.chr(39).'|\\\\)*'.chr(39).'/
|
||||
name = /([_a-zA-Z][_a-zA-Z0-9]*|`[^`]+`)/
|
||||
varname = /:([_a-zA-Z][_a-zA-Z0-9]*->[_a-zA-Z][_a-zA-Z0-9]*|[_a-zA-Z][_a-zA-Z0-9]*)/
|
||||
dot = "."
|
||||
dot = "."
|
||||
*/
|
||||
|
||||
/*!lex2php
|
||||
@@ -198,6 +200,12 @@ join {
|
||||
on {
|
||||
$this->token = OQLParser::ON;
|
||||
}
|
||||
arrow {
|
||||
$this->token = OQLParser::ARROW;
|
||||
}
|
||||
colon {
|
||||
$this->token = OQLParser::COLON;
|
||||
}
|
||||
math_div {
|
||||
$this->token = OQLParser::MATH_DIV;
|
||||
}
|
||||
@@ -396,6 +404,7 @@ varname {
|
||||
dot {
|
||||
$this->token = OQLParser::DOT;
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -154,8 +154,16 @@ scalar(A) ::= str_scalar(X). { A = X; }
|
||||
num_scalar(A) ::= num_value(X). { A = new ScalarOqlExpression(X); }
|
||||
str_scalar(A) ::= str_value(X). { A = new ScalarOqlExpression(X); }
|
||||
|
||||
field_id(A) ::= name(X). { A = new FieldOqlExpression(X); }
|
||||
field_id(A) ::= class_name(X) DOT name(Y). { A = new FieldOqlExpression(Y, X); }
|
||||
basic_field_id(A) ::= name(X). { A = new FieldOqlExpression(X); }
|
||||
basic_field_id(A) ::= class_name(X) DOT name(Y). { A = new FieldOqlExpression(Y, X); }
|
||||
|
||||
field_id(A) ::= basic_field_id(X). { A = X; }
|
||||
field_id(A) ::= field_id(X) ARROW name(Y).
|
||||
{
|
||||
$expr = new FieldOqlExpression(Y);
|
||||
A = new ExternalFieldOqlExpression(X, $expr);
|
||||
}
|
||||
|
||||
class_name(A) ::= name(X). { A=X; }
|
||||
|
||||
|
||||
|
||||
@@ -168,9 +168,108 @@ class ScalarOqlExpression extends ScalarExpression implements CheckableExpressio
|
||||
}
|
||||
}
|
||||
|
||||
class ExternalFieldOqlExpression extends ExternalFieldExpression implements CheckableExpression
|
||||
{
|
||||
protected $m_aExpression = array();
|
||||
protected $m_sName = null;
|
||||
|
||||
|
||||
function __construct($oExpr1, $oExpr2)
|
||||
{
|
||||
$this->m_sName = '';
|
||||
|
||||
if ($oExpr1 instanceof ExternalFieldOqlExpression)
|
||||
{
|
||||
$this->m_aExpression = $oExpr1->GetExpressions();
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->m_aExpression[] = $oExpr1;
|
||||
}
|
||||
$this->m_aExpression[] = $oExpr2;
|
||||
$this->m_sName = $oExpr1->GetValue().'->'.$oExpr2->GetValue();
|
||||
|
||||
|
||||
parent::__construct($this->m_sName, $this->m_aExpression);
|
||||
}
|
||||
|
||||
public function GetExpressions()
|
||||
{
|
||||
return $this->m_aExpression;
|
||||
}
|
||||
|
||||
public function GetName()
|
||||
{
|
||||
return $this->m_sName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the validity of the expression with regard to the data model
|
||||
* and the query in which it is used
|
||||
*
|
||||
* @param ModelReflection $oModelReflection MetaModel to consider
|
||||
* @param array $aAliases Aliases to class names (for the current query)
|
||||
* @param string $sSourceQuery For the reporting
|
||||
*
|
||||
* @throws OqlNormalizeException
|
||||
*/
|
||||
public function Check(ModelReflection $oModelReflection, $aAliases, $sSourceQuery)
|
||||
{
|
||||
$sParentAlias = null;
|
||||
foreach($this->m_aExpression as $i => $oFieldOqlExpression)
|
||||
{
|
||||
|
||||
if (is_null($sParentAlias))
|
||||
{
|
||||
$oFieldOqlExpression->RefreshAlias($oModelReflection, $aAliases, $sSourceQuery, FieldOqlExpression::ALLOW_EXTERNAL_FIELDS);
|
||||
}
|
||||
else
|
||||
{
|
||||
$oFieldOqlExpression->SetParent($sParentAlias);
|
||||
}
|
||||
$oFieldOqlExpression->Check($oModelReflection, $aAliases, $sSourceQuery);
|
||||
|
||||
$sClass = $aAliases[$oFieldOqlExpression->GetParent()];
|
||||
|
||||
$bLastIteration = ($i == (count($this->m_aExpression) - 1));
|
||||
|
||||
if (!$bLastIteration)
|
||||
{
|
||||
if ($oFieldOqlExpression->GetName() == 'id')
|
||||
{
|
||||
$sTargetClass = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
$sTargetClass = $oModelReflection->GetAttributeProperty($sClass, $oFieldOqlExpression->GetName(), 'targetclass');
|
||||
}
|
||||
|
||||
if (is_null($sTargetClass))
|
||||
{
|
||||
throw new OqlNormalizeException('Forbidden operation for attribute', $sSourceQuery, $oFieldOqlExpression->GetNameDetails(), $oModelReflection->GetFiltersList($sClass));
|
||||
}
|
||||
$aAliases[$sTargetClass] = $sTargetClass;
|
||||
$sParentAlias = $sTargetClass;
|
||||
}
|
||||
else
|
||||
{
|
||||
if ($oFieldOqlExpression->GetName() != 'id') //on lastIteration, the id field can be used, but since it is not supporter by IsValidAttCode we must avoid it
|
||||
{
|
||||
if (!$oModelReflection->IsValidAttCode($sClass, $oFieldOqlExpression->GetName()))
|
||||
{
|
||||
throw new OqlNormalizeException('Invalid attribute', $sSourceQuery, $oFieldOqlExpression->GetNameDetails(), $oModelReflection->GetFiltersList($sClass));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class FieldOqlExpression extends FieldExpression implements CheckableExpression
|
||||
{
|
||||
protected $m_oParent;
|
||||
const ALLOW_EXTERNAL_FIELDS = true;
|
||||
const DISALLOW_EXTERNAL_FIELDS = false;
|
||||
protected $m_oParent;
|
||||
protected $m_oName;
|
||||
|
||||
public function __construct($oName, $oParent = null)
|
||||
@@ -203,23 +302,7 @@ class FieldOqlExpression extends FieldExpression implements CheckableExpression
|
||||
{
|
||||
// Try to find an alias
|
||||
// Build an array of field => array of aliases
|
||||
$aFieldClasses = array();
|
||||
foreach($aAliases as $sAlias => $sReal)
|
||||
{
|
||||
foreach($oModelReflection->GetFiltersList($sReal) as $sAnFltCode)
|
||||
{
|
||||
$aFieldClasses[$sAnFltCode][] = $sAlias;
|
||||
}
|
||||
}
|
||||
if (!array_key_exists($sFltCode, $aFieldClasses))
|
||||
{
|
||||
throw new OqlNormalizeException('Unknown filter code', $sSourceQuery, $this->GetNameDetails(), array_keys($aFieldClasses));
|
||||
}
|
||||
if (count($aFieldClasses[$sFltCode]) > 1)
|
||||
{
|
||||
throw new OqlNormalizeException('Ambiguous filter code', $sSourceQuery, $this->GetNameDetails());
|
||||
}
|
||||
$sClassAlias = $aFieldClasses[$sFltCode][0];
|
||||
$this->RefreshAlias($oModelReflection, $aAliases, $sSourceQuery);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -234,6 +317,62 @@ class FieldOqlExpression extends FieldExpression implements CheckableExpression
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Used by self::Check and ExternalFieldOqlExpression::Check => throws exception if error
|
||||
*
|
||||
* this method has a side effect : if not previously set $this->m_sParent if computed then set.
|
||||
*
|
||||
* @param \ModelReflection $oModelReflection
|
||||
* @param $aAliases
|
||||
* @param $sSourceQuery
|
||||
* @param bool $bAllowExternalFields should external fields be authorised? used only by @see ExternalFieldOqlExpression
|
||||
*
|
||||
* @throws OqlNormalizeException
|
||||
*/
|
||||
public function RefreshAlias(ModelReflection $oModelReflection, $aAliases, $sSourceQuery, $bAllowExternalFields = self::DISALLOW_EXTERNAL_FIELDS)
|
||||
{
|
||||
$sClassAlias = $this->GetParent();
|
||||
$sFltCode = $this->GetName();
|
||||
if (empty($sClassAlias))
|
||||
{
|
||||
// Try to find an alias
|
||||
// Build an array of field => array of aliases
|
||||
$aFieldClasses = array();
|
||||
foreach($aAliases as $sAlias => $sReal)
|
||||
{
|
||||
foreach($oModelReflection->GetFiltersList($sReal) as $sAnFltCode)
|
||||
{
|
||||
$aFieldClasses[$sAnFltCode][] = $sAlias;
|
||||
}
|
||||
}
|
||||
if (!array_key_exists($sFltCode, $aFieldClasses))
|
||||
{
|
||||
if (count($aAliases) > 1)
|
||||
{
|
||||
throw new OqlNormalizeException('Ambiguous filter code', $sSourceQuery, $this->GetNameDetails(), array_keys($aFieldClasses));
|
||||
}
|
||||
|
||||
$sClassAlias = reset($aAliases);
|
||||
if (self::DISALLOW_EXTERNAL_FIELDS == $bAllowExternalFields || false == $oModelReflection->IsValidAttCode($sClassAlias, $sFltCode))
|
||||
{
|
||||
throw new OqlNormalizeException('Unknown filter code', $sSourceQuery, $this->GetNameDetails(), array_keys($aFieldClasses));
|
||||
}
|
||||
$this->SetParent($sClassAlias);
|
||||
|
||||
}
|
||||
elseif (count($aFieldClasses[$sFltCode]) > 1)
|
||||
{
|
||||
throw new OqlNormalizeException('Ambiguous filter code', $sSourceQuery, $this->GetNameDetails());
|
||||
}
|
||||
else
|
||||
{
|
||||
$sClassAlias = $aFieldClasses[$sFltCode][0];
|
||||
$this->SetParent($sClassAlias);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class VariableOqlExpression extends VariableExpression implements CheckableExpression
|
||||
|
||||
@@ -1 +1 @@
|
||||
2015-08-31
|
||||
2018-04-26
|
||||
3387
css/light-grey.css
3387
css/light-grey.css
File diff suppressed because it is too large
Load Diff
134
test/core/oql/OqlInterpreterTest.php
Normal file
134
test/core/oql/OqlInterpreterTest.php
Normal file
@@ -0,0 +1,134 @@
|
||||
<?php
|
||||
/**
|
||||
* Created by PhpStorm.
|
||||
* User: Eric
|
||||
* Date: 27/04/2018
|
||||
* Time: 09:52
|
||||
*/
|
||||
|
||||
namespace Combodo\iTop\Test\UnitTest\Core\Oql;
|
||||
|
||||
use DBObjectSearch;
|
||||
use DBObjectSet;
|
||||
use ExpressionCache;
|
||||
use OqlInterpreter;
|
||||
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
|
||||
use OqlNormalizeException;
|
||||
use OQLParserException;
|
||||
|
||||
class OqlInterpreterTest extends ItopDataTestCase
|
||||
{
|
||||
/**
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function setUp()
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
require_once(APPROOT.'/core/cmdbobject.class.inc.php');
|
||||
require_once(APPROOT."core/oql/oqlinterpreter.class.inc.php");
|
||||
require_once(APPROOT.'core/dbobject.class.php');
|
||||
require_once(APPROOT."core/dbobjectsearch.class.php");
|
||||
require_once(APPROOT.'core/dbobjectset.class.php');
|
||||
require_once(APPROOT."core/modelreflection.class.inc.php");
|
||||
|
||||
require_once(APPROOT."core/expressioncache.class.inc.php");
|
||||
$sCacheFileName = ExpressionCache::GetCacheFileName();
|
||||
unlink($sCacheFileName);
|
||||
ExpressionCache::Warmup();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public function testShorthandExpansionCloningOption()
|
||||
{
|
||||
$sQuery = "SELECT Contact WHERE org_id->deliverymodel_id->name = 'Standard support' AND 1 AND 1 AND 1";
|
||||
|
||||
$oDbObjectSearch = DBObjectSearch::FromOQL($sQuery);
|
||||
$oDbObjectSearchExpandedClone1 = $oDbObjectSearch->ShorthandExpansion(true);
|
||||
$oDbObjectSearchExpandedClone2 = $oDbObjectSearch->ShorthandExpansion(true);
|
||||
$oDbObjectSearchExpandedBase1 = $oDbObjectSearch->ShorthandExpansion();
|
||||
$oDbObjectSearchExpandedBase2 = $oDbObjectSearch->ShorthandExpansion();
|
||||
|
||||
$this->assertSame($oDbObjectSearchExpandedBase1, $oDbObjectSearch);
|
||||
$this->assertSame($oDbObjectSearchExpandedBase2, $oDbObjectSearch);
|
||||
|
||||
$this->assertNotSame($oDbObjectSearchExpandedClone1, $oDbObjectSearch);
|
||||
$this->assertNotSame($oDbObjectSearchExpandedClone2, $oDbObjectSearch);
|
||||
|
||||
$this->assertNotSame($oDbObjectSearchExpandedClone1, $oDbObjectSearchExpandedClone2);
|
||||
|
||||
$this->debug($oDbObjectSearch->ToOQL());
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider ShorthandExpansionProvider
|
||||
* @param $sQuery
|
||||
*/
|
||||
public function testShorthandExpansion($sQuery, $sExpectedException, $sExpectedOqlEquals, $aExpectedSqlContains)
|
||||
{
|
||||
|
||||
if (!empty($sExpectedException)) {
|
||||
$this->expectException($sExpectedException);
|
||||
}
|
||||
|
||||
$oDbObjectSearch = DBObjectSearch::FromOQL($sQuery);
|
||||
|
||||
$sSql = $oDbObjectSearch->MakeSelectQuery();
|
||||
$sOql = $oDbObjectSearch->ToOQL();
|
||||
$oSet = new DBObjectSet($oDbObjectSearch);
|
||||
$iCount = $oSet->Count();
|
||||
|
||||
$this->assertInternalType('numeric', $iCount);
|
||||
$this->assertEquals($sExpectedOqlEquals, $sOql);
|
||||
foreach ($aExpectedSqlContains as $sExpectedSqlContain)
|
||||
{
|
||||
$this->assertContains($sExpectedSqlContain, $sSql);
|
||||
}
|
||||
|
||||
$this->debug($sSql);
|
||||
$this->debug($sOql);
|
||||
}
|
||||
|
||||
public function ShorthandExpansionProvider()
|
||||
{
|
||||
return array(
|
||||
array("SELECT Contact WHERE org_id->deliverymodel_id->name = 'Standard support'", false, 'SELECT `Contact` FROM Contact AS `Contact` WHERE (Contact.org_id->deliverymodel_id->name = \'Standard support\')', array(
|
||||
'JOIN (`organization`',
|
||||
'JOIN `deliverymodel`',
|
||||
)),
|
||||
array('SELECT Contact WHERE org_id->deliverymodel_id->contacts_list->role_id->name != ""', OqlNormalizeException::class, '', array()),
|
||||
array('SELECT Contact WHERE cis_list->name = "Cluster1"', OqlNormalizeException::class, '', array()),
|
||||
array('SELECT Contact WHERE cis_list->name LIKE "%m%"', OqlNormalizeException::class, '', array()),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @dataProvider ParseProvider
|
||||
* @param $sQuery
|
||||
*/
|
||||
public function testParse($sQuery)
|
||||
{
|
||||
$oOql = new OqlInterpreter($sQuery);
|
||||
$oTrash = $oOql->Parse(); // Not expecting a given format, otherwise use ParseExpression/ParseObjectQuery/ParseValueSetQuery
|
||||
|
||||
$this->assertInstanceOf(\OqlObjectQuery::class, $oTrash);
|
||||
|
||||
|
||||
$this->debug($sQuery);
|
||||
}
|
||||
|
||||
public function ParseProvider()
|
||||
{
|
||||
return array(
|
||||
array("SELECT Contact WHERE org_id->deliverymodel_id->name = 'Standard support'"),
|
||||
array("SELECT Contact WHERE org_id->foo->bar LIKE 'Standard support'"),
|
||||
array("SELECT Contact WHERE cis_list->name = 3"),
|
||||
array("SELECT Contact WHERE cis_list->name < 3"),
|
||||
array("SELECT Contact WHERE cis_list->name >- 3"),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user