Compare commits

...

10 Commits

Author SHA1 Message Date
Bruno Da Silva
e508f645f3 advanced search: ShorthandExpansion TODOs list
SVN:b1312[5765]
2018-05-02 12:19:38 +00:00
Bruno Da Silva
81f7f9d70b advanced search: ShorthandExpansion tests
- tests (rely on generated SQL/OQL strings)
- removal of a BC break (added an optional parameter $bAllowExternalFields to FieldOqlExpression::RefreshAlias() )

SVN:b1312[5764]
2018-05-02 08:51:36 +00:00
Bruno Da Silva
59c9a98b87 advanced search: ShorthandExpansion on conversion To SQL
- plus tests
- plus implementation of ExternalFieldExpression->Render()

SVN:b1312[5760]
2018-04-27 16:15:08 +00:00
Eric Espié
d57fb3e24e Parser for Short Hand Syntax: unit tests
SVN:b1312[5753]
2018-04-27 13:26:56 +00:00
Bruno Da Silva
88ae239a56 advanced search: ExternalFieldOqlExpression Check method
- handling of special case for the attribute "id"
- clearer error message
- typo

SVN:b1312[5749]
2018-04-26 15:54:34 +00:00
Eric Espié
30494756fc Parser for Short Hand Syntax
SVN:b1312[5748]
2018-04-26 15:26:41 +00:00
Eric Espié
1900546440 Parser for Short Hand Syntax (->) Check method
SVN:b1312[5747]
2018-04-26 14:59:30 +00:00
Bruno Da Silva
d8dd0437b3 advanced search: ExternalFieldOqlExpression basic constructor
SVN:b1312[5746]
2018-04-26 13:38:19 +00:00
Eric Espié
d1d50e0448 Parser for Short Hand Syntax (->)
SVN:b1312[5745]
2018-04-26 13:12:42 +00:00
Eric Espié
1c87c15222 SVN:b1312[5740] 2018-04-26 08:56:13 +00:00
10 changed files with 2964 additions and 2263 deletions

View File

@@ -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);

View File

@@ -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;

View File

@@ -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;

View File

@@ -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

View File

@@ -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; }

View File

@@ -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

View File

@@ -1 +1 @@
2015-08-31
2018-04-26

File diff suppressed because it is too large Load Diff

View 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"),
);
}
}