OQL: Fixed a number of bugs, and implemented new features

- bug: a JOIN b on b.extkey = a.id
- bug: operators precedence (still a shift-reduce conflict with JOINS)
- changed pkey into id (preserved the compatibility for DBObjectSearch::AddCondition()
- allow implicit class name in WHERE condition
- bug: wrong report on typo error
- suggest an alternative in case of typo error

SVN:code[12]
This commit is contained in:
Romain Quetiez
2009-03-27 14:36:14 +00:00
parent a0f4fdb130
commit 8d9ea9dcdd
15 changed files with 989 additions and 612 deletions

View File

@@ -94,6 +94,113 @@ class MyHelpers
echo "\n</pre>\n";
}
public static function var_dump_string($var)
{
ob_start();
print_r($var);
$sRet = ob_get_clean();
return $sRet;
}
protected static function first_diff_line($s1, $s2)
{
$aLines1 = explode("\n", $s1);
$aLines2 = explode("\n", $s2);
for ($i = 0 ; $i < min(count($aLines1), count($aLines2)) ; $i++)
{
if ($aLines1[$i] != $aLines2[$i]) return $i;
}
return false;
}
protected static function highlight_line($sMultiline, $iLine, $sHighlightStart = '<b>', $sHightlightEnd = '</b>')
{
$aLines = explode("\n", $sMultiline);
$aLines[$iLine] = $sHighlightStart.$aLines[$iLine].$sHightlightEnd;
return implode("\n", $aLines);
}
protected static function first_diff($s1, $s2)
{
// do not work fine with multiline strings
$iLen1 = strlen($s1);
$iLen2 = strlen($s2);
for ($i = 0 ; $i < min($iLen1, $iLen2) ; $i++)
{
if ($s1[$i] !== $s2[$i]) return $i;
}
return false;
}
protected static function last_diff($s1, $s2)
{
// do not work fine with multiline strings
$iLen1 = strlen($s1);
$iLen2 = strlen($s2);
for ($i = 0 ; $i < min(strlen($s1), strlen($s2)) ; $i++)
{
if ($s1[$iLen1 - $i - 1] !== $s2[$iLen2 - $i - 1]) return array($iLen1 - $i, $iLen2 - $i);
}
return false;
}
protected static function text_cmp_html($sText1, $sText2, $sHighlight)
{
$iDiffPos = self::first_diff_line($sText1, $sText2);
$sDisp1 = self::highlight_line($sText1, $iDiffPos, '<div style="'.$sHighlight.'">', '</div>');
$sDisp2 = self::highlight_line($sText2, $iDiffPos, '<div style="'.$sHighlight.'">', '</div>');
echo "<table style=\"valign=top;\">\n";
echo "<tr>\n";
echo "<td><pre>$sDisp1</pre></td>\n";
echo "<td><pre>$sDisp2</pre></td>\n";
echo "</tr>\n";
echo "</table>\n";
}
protected static function string_cmp_html($s1, $s2, $sHighlight)
{
$iDiffPos = self::first_diff($s1, $s2);
if ($iDiffPos === false)
{
echo "strings are identical";
return;
}
$sStart = substr($s1, 0, $iDiffPos);
$aLastDiff = self::last_diff($s1, $s2);
$sEnd = substr($s1, $aLastDiff[0]);
$sMiddle1 = substr($s1, $iDiffPos, $aLastDiff[0] - $iDiffPos);
$sMiddle2 = substr($s2, $iDiffPos, $aLastDiff[1] - $iDiffPos);
echo "<p>$sStart<span style=\"$sHighlight\">$sMiddle1</span>$sEnd</p>\n";
echo "<p>$sStart<span style=\"$sHighlight\">$sMiddle2</span>$sEnd</p>\n";
}
protected static function object_cmp_html($oObj1, $oObj2, $sHighlight)
{
$sObj1 = self::var_dump_string($oObj1);
$sObj2 = self::var_dump_string($oObj2);
return self::text_cmp_html($sObj1, $sObj2, $sHighlight);
}
public static function var_cmp_html($var1, $var2, $sHighlight = 'color:red; font-weight:bold;')
{
if (is_object($var1))
{
return self::object_cmp_html($var1, $var2, $sHighlight);
}
else if (count(explode("\n", $var1)) > 1)
{
// multiline string
return self::text_cmp_html($var1, $var2, $sHighlight);
}
else
{
return self::string_cmp_html($var1, $var2, $sHighlight);
}
}
public static function get_callstack_html($iLevelsToIgnore = 0, $aCallStack = null)
{
if ($aCallStack == null) $aCallStack = debug_backtrace();
@@ -176,6 +283,7 @@ class MyHelpers
{
if (!is_array($aData)) trigger_error("make_table_from_assoc_array: Error - the passed argument is not an array", E_USER_ERROR);
$aFirstRow = reset($aData);
if (count($aData) == 0) return '';
if (!is_array($aFirstRow)) trigger_error("make_table_from_assoc_array: Error - the passed argument is not a bi-dimensional array", E_USER_ERROR);
$sOutput = "";
$sOutput .= "<TABLE WIDTH=\"100%\" BORDER=\"0\" CELLSPACING=\"1\" CELLPADDING=\"1\">\n";

View File

@@ -113,7 +113,7 @@ abstract class DBObject
$aRow = MetaModel::MakeSingleRow(get_class($this), $this->m_iKey);
if (empty($aRow))
{
trigger_error("Failed to reload object of class '".get_class($this)."', pkey = ".$this->m_iKey, E_USER_ERROR);
trigger_error("Failed to reload object of class '".get_class($this)."', id = ".$this->m_iKey, E_USER_ERROR);
}
$this->FromRow($aRow);
@@ -133,7 +133,7 @@ abstract class DBObject
$sMyClass = $oRemoteExtKeyAtt->GetTargetClass();
$oMyselfSearch = new DBObjectSearch($sMyClass);
$oMyselfSearch->AddCondition('pkey', $this->m_iKey, '=');
$oMyselfSearch->AddCondition('id', $this->m_iKey, '=');
$oLinkSearch = new DBObjectSearch($sLinkClass);
$oLinkSearch->AddCondition_PointingTo($oMyselfSearch, $sExtKeyToMe);
@@ -157,7 +157,7 @@ abstract class DBObject
// Get the key
//
$sKeyField = "pkey";
$sKeyField = "id";
if (!array_key_exists($sKeyField, $aRow))
{
// #@# Bug ?
@@ -168,7 +168,7 @@ abstract class DBObject
$iPKey = $aRow[$sKeyField];
if (!self::IsValidPKey($iPKey))
{
trigger_error("An object PKey must be an integer value ($iPKey)", E_USER_NOTICE);
trigger_error("An object id must be an integer value ($iPKey)", E_USER_NOTICE);
}
$this->m_iKey = $iPKey;
}
@@ -367,7 +367,7 @@ abstract class DBObject
{
if (!self::IsValidPKey($iNewKey))
{
trigger_error("An object PKey must be an integer value ($iNewKey)", E_USER_ERROR);
trigger_error("An object id must be an integer value ($iNewKey)", E_USER_ERROR);
}
if ($this->m_bIsInDB && !empty($this->m_iKey) && ($this->m_iKey != $iNewKey))
@@ -660,7 +660,7 @@ abstract class DBObject
if (count($aChanges) != 0)
{
$oFilter = new DBObjectSearch(get_class($this));
$oFilter->AddCondition('pkey', $this->m_iKey, '=');
$oFilter->AddCondition('id', $this->m_iKey, '=');
$sSQL = MetaModel::MakeUpdateQuery($oFilter, $aChanges);
CMDBSource::Query($sSQL);
@@ -691,7 +691,7 @@ abstract class DBObject
public function DBDelete()
{
$oFilter = new DBObjectSearch(get_class($this));
$oFilter->AddCondition('pkey', $this->m_iKey, '=');
$oFilter->AddCondition('id', $this->m_iKey, '=');
$sSQL = MetaModel::MakeDeleteQuery($oFilter);
CMDBSource::Query($sSQL);

View File

@@ -162,7 +162,7 @@ class DBObjectSearch
public function __DescribeHTML()
{
$sConditionDesc = $this->DescribeConditions();
$sConditionDesc = $this->DescribeConditions();
if (!empty($sConditionDesc))
{
return "Objects of class '$this->m_sClass', $sConditionDesc";
@@ -179,8 +179,8 @@ class DBObjectSearch
public function ResetCondition()
{
$this->m_oSearchCondition = new TrueExpression();
// ? is that enough, do I need to rebuild the list after the subqueries ?
$this->m_aClasses = array($this->m_sClassAlias => $this->m_sClass);
// ? is that usefull/enough, do I need to rebuild the list after the subqueries ?
// $this->m_aClasses = array($this->m_sClassAlias => $this->m_sClass);
}
public function AddConditionExpression($oExpression)
@@ -190,6 +190,11 @@ class DBObjectSearch
public function AddCondition($sFilterCode, $value, $sOpCode = null)
{
// #@# backward compatibility for pkey/id
if (strtolower(trim($sFilterCode)) == 'pkey') $sFilterCode = 'id';
// #@# todo - obsolete smoothly, first send exceptions
// throw new CoreException('SibusQL has been obsoleted, please update your queries', array('sibusql'=>$sQuery, 'oql'=>$oFilter->ToOQL()));
MyHelpers::CheckKeyInArray('filter code', $sFilterCode, MetaModel::GetClassFilterDefs($this->m_sClass));
$oFilterDef = MetaModel::GetClassFilterDef($this->m_sClass, $sFilterCode);
@@ -215,11 +220,13 @@ class DBObjectSearch
break;
case "IN":
if (!is_array($value)) $value = array($value);
$sListExpr = '('.implode(', ', CMDBSource::Quote($value)).')';
$sOQLCondition = $oField->Render()." IN $sListExpr";
break;
case "NOTIN":
if (!is_array($value)) $value = array($value);
$sListExpr = '('.implode(', ', CMDBSource::Quote($value)).')';
$sOQLCondition = $oField->Render()." NOT IN $sListExpr";
break;
@@ -271,7 +278,7 @@ class DBObjectSearch
$sOrigAlias = $this->m_sClassAlias;
if (array_key_exists($sOrigAlias, $aClassAliases))
{
$this->m_sClassAlias = MetaModel::GenerateUniqueAlias($aClassAliases, $sOrigAlias, $oFilter->GetClass());
$this->m_sClassAlias = MetaModel::GenerateUniqueAlias($aClassAliases, $sOrigAlias, $this->m_sClass);
// Translate the condition expression with the new alias
$aAliasTranslation[$sOrigAlias]['*'] = $this->m_sClassAlias;
}
@@ -316,19 +323,13 @@ class DBObjectSearch
}
else
{
$sOrigAlias = $oFilter->GetClassAlias();
$sKeyClassAlias = MetaModel::GenerateUniqueAlias($aClassAliases, $sOrigAlias, $oFilter->GetClass());
if ($sKeyClassAlias != $sOrigAlias)
{
// Translate the condition expression with the new alias
$aAliasTranslation[$sOrigAlias]['*'] = $sKeyClassAlias;
}
$oFilter->AddToNamespace($aClassAliases, $aAliasTranslation);
// #@# The condition expression found in that filter should not be used - could be another kind of structure like a join spec tree !!!!
$oNewFilter = clone $oFilter;
$oNewFilter->ResetCondition();
// $oNewFilter = clone $oFilter;
// $oNewFilter->ResetCondition();
$this->m_aPointingTo[$sExtKeyAttCode] = $oNewFilter;
$this->m_aPointingTo[$sExtKeyAttCode] = $oFilter;
}
}
@@ -340,7 +341,7 @@ class DBObjectSearch
return $res;
}
public function AddCondition_ReferencedBy_InNameSpace(DBObjectSearch $oFilter, $sForeignExtKeyAttCode, &$aClassAliases, &$aAliasTranslation)
protected function AddCondition_ReferencedBy_InNameSpace(DBObjectSearch $oFilter, $sForeignExtKeyAttCode, &$aClassAliases, &$aAliasTranslation)
{
$sForeignClass = $oFilter->GetClass();
$sForeignClassAlias = $oFilter->GetClassAlias();
@@ -359,19 +360,13 @@ class DBObjectSearch
}
else
{
$sOrigAlias = $oFilter->GetClassAlias();
$sKeyClassAlias = MetaModel::GenerateUniqueAlias($aClassAliases, $sOrigAlias, $oFilter->GetClass());
if ($sKeyClassAlias != $sOrigAlias)
{
// Translate the condition expression with the new alias
$aAliasTranslation[$sOrigAlias]['*'] = $sKeyClassAlias;
}
$oFilter->AddToNamespace($aClassAliases, $aAliasTranslation);
// #@# The condition expression found in that filter should not be used - could be another kind of structure like a join spec tree !!!!
$oNewFilter = clone $oFilter;
$oNewFilter->ResetCondition();
//$oNewFilter = clone $oFilter;
//$oNewFilter->ResetCondition();
$this->m_aReferencedBy[$sForeignClass][$sForeignExtKeyAttCode]= $oNewFilter;
$this->m_aReferencedBy[$sForeignClass][$sForeignExtKeyAttCode]= $oFilter;
}
}
@@ -593,47 +588,36 @@ class DBObjectSearch
return $retValue;
}
public function ToSibusQL()
public function ToOQL()
{
$aConds = array(); // string conditions, to be merged into a logical AND
foreach($this->m_aFullText as $sFullText)
{
$aConds[] = "* HAS ".self::Value2Expression($sFullText);
}
// #@# todo - changer ToSibusQL en ToOQL et l'implementer !
// $aConds[] = $this->m_oSearchCondition->ToSibusQL
/*
foreach($this->m_aCriteria as $aCritInfo)
{
$aConds[] = $aCritInfo["filtercode"]." ".$aCritInfo["opcode"]." ".self::Value2Expression($aCritInfo["value"]);
}
*/
$sRes = "SELECT ".$this->GetClass().' AS '.$this->GetClassAlias();
$sRes .= $this->ToOQL_Joins();
$sRes .= " WHERE ".$this->m_oSearchCondition->Render();
return $sRes;
}
protected function ToOQL_Joins()
{
$sRes = '';
foreach($this->m_aPointingTo as $sExtKey=>$oFilter)
{
$aConds[] = $sExtKey." IN (".$oFilter->ToSibusQL().")";
$sRes .= ' JOIN '.$oFilter->GetClass().' AS '.$oFilter->GetClassAlias().' ON '.$this->GetClassAlias().'.'.$sExtKey.' = '.$oFilter->GetClassAlias().'.id';
$sRes .= $oFilter->ToOQL_Joins();
}
foreach($this->m_aReferencedBy as $sForeignClass=>$aReferences)
{
foreach($aReferences as $sForeignExtKeyAttCode=>$oForeignFilter)
{
$aConds[] = "PKEY IS ".$sForeignExtKeyAttCode." IN (".$oForeignFilter->ToSibusQL().")";
$sRes .= ' JOIN '.$oForeignFilter->GetClass().' AS '.$oForeignFilter->GetClassAlias().' ON '.$oForeignFilter->GetClassAlias().'.'.$sForeignExtKeyAttCode.' = '.$this->GetClassAlias().'.id';
$sRes .= $oForeignFilter->ToOQL_Joins();
}
}
foreach($this->m_aRelatedTo as $aRelatedTo)
{
$oFilter = $aRelatedTo['flt'];
$sRelCode = $aRelatedTo['relcode'];
$iMaxDepth = $aRelatedTo['maxdepth'];
$aConds[] = "RELATED ($sRelCode, $iMaxDepth) TO (".$oFilter->ToSibuSQL().")";
}
return $sRes;
}
$sValue = $this->GetClass();
if (count($aConds) > 0)
{
$sValue .= ": ".implode(" AND ", $aConds);
}
return $sValue;
public function ToSibusQL()
{
return "NONONO";
}
static private function privProcessParams($sQuery, array $aParams, $oDbObject)
@@ -659,7 +643,7 @@ class DBObjectSearch
if (strpos($sParameterName, "this.") === 0)
{
$sAttCode = substr($sParameterName, strlen("this."));
if ($sAttCode == 'pkey')
if ($sAttCode == 'id')
{
$sValue = $oDbObject->GetKey();
}
@@ -717,19 +701,37 @@ class DBObjectSearch
$sFltCode = $oExpression->GetName();
if (empty($sClassAlias))
{
$iPos = $oExpression->GetPosition();
throw new OqlNormalizeException('Missing class specification', $sQuery, 0, $iPos, '');
// Try to find an alias
// Build an array of field => array of aliases
$aFieldClasses = array();
foreach($aClassAliases as $sAlias => $sReal)
{
foreach(MetaModel::GetFiltersList($sReal) as $sAnFltCode)
{
$aFieldClasses[$sAnFltCode][] = $sAlias;
}
}
if (!array_key_exists($sFltCode, $aFieldClasses))
{
throw new OqlNormalizeException('Unknown filter code', $sQuery, $oExpression->GetNameDetails(), array_keys($aFieldClasses));
}
if (count($aFieldClasses[$sFltCode]) > 1)
{
throw new OqlNormalizeException('Ambiguous filter code', $sQuery, $oExpression->GetNameDetails());
}
$sClassAlias = $aFieldClasses[$sFltCode][0];
}
if (!array_key_exists($sClassAlias, $aClassAliases))
else
{
$iPos = $oExpression->GetPosition();
throw new OqlNormalizeException('Unknown class', $sQuery, 0, $iPos, $sClassAlias, array_keys($aClassAliases));
}
$sClass = $aClassAliases[$sClassAlias];
if (!MetaModel::IsValidFilterCode($sClass, $sFltCode))
{
$iPos = $oExpression->GetPosition();
throw new OqlNormalizeException('Unknown filter code', $sQuery, 0, $iPos, "$sFltCode in class $sClassAlias", MetaModel::GetFiltersList($sClass));
if (!array_key_exists($sClassAlias, $aClassAliases))
{
throw new OqlNormalizeException('Unknown class [alias]', $sQuery, $oExpression->GetParentDetails(), array_keys($aClassAliases));
}
$sClass = $aClassAliases[$sClassAlias];
if (!MetaModel::IsValidFilterCode($sClass, $sFltCode))
{
throw new OqlNormalizeException('Unknown filter code', $sQuery, $oExpression->GetNameDetails(), MetaModel::GetFiltersList($sClass));
}
}
return new FieldExpression($sFltCode, $sClassAlias);
@@ -768,11 +770,11 @@ class DBObjectSearch
if (!MetaModel::IsValidClass($sClass))
{
throw new OqlNormalizeException('Unknown class', $sQuery, 0, 0, $sClass, MetaModel::GetClasses());
throw new OqlNormalizeException('Unknown class', $sQuery, $oOqlQuery->GetClassDetails(), MetaModel::GetClasses());
}
$oResultFilter = new DBObjectSearch($sClass, $sClassAlias);
$oResultFilter->m_aClasses = array($sClassAlias => $sClass);
$aAliases = array($sClassAlias => $sClass);
// Maintain an array of filters, because the flat list is in fact referring to a tree
// And this will be an easy way to dispatch the conditions
@@ -788,17 +790,17 @@ class DBObjectSearch
$sJoinClassAlias = $oJoinSpec->GetClassAlias();
if (!MetaModel::IsValidClass($sJoinClass))
{
throw new OqlNormalizeException('Unknown class', $sQuery, 0, 0, $sJoinClass, MetaModel::GetClasses());
throw new OqlNormalizeException('Unknown class', $sQuery, $oJoinSpec->GetClassDetails(), MetaModel::GetClasses());
}
if (array_key_exists($sJoinClassAlias, $oResultFilter->m_aClasses))
if (array_key_exists($sJoinClassAlias, $aAliases))
{
if ($sJoinClassAlias != $sJoinClass)
{
throw new OqlNormalizeException('Duplicate class alias', $sQuery, 0, 0, $sJoinClassAlias);
throw new OqlNormalizeException('Duplicate class alias', $sQuery, $oJoinSpec->GetClassAliasDetails());
}
else
{
throw new OqlNormalizeException('Duplicate class name', $sQuery, 0, 0, $sJoinClass);
throw new OqlNormalizeException('Duplicate class name', $sQuery, $oJoinSpec->GetClassDetails());
}
}
@@ -813,24 +815,24 @@ class DBObjectSearch
$sPKeyDescriptor = $oRightField->GetName();
if ($sPKeyDescriptor != 'id')
{
throw new OqlNormalizeException('Wrong format for Join clause (right hand), expecting an id', $sQuery, 0, $oRightField->GetPosition(), $sPKeyDescriptor, array('id'));
throw new OqlNormalizeException('Wrong format for Join clause (right hand), expecting an id', $sQuery, $oRightField->GetNameDetails(), array('id'));
}
$oResultFilter->m_aClasses[$sJoinClassAlias] = $sJoinClass;
$aAliases[$sJoinClassAlias] = $sJoinClass;
$aJoinItems[$sJoinClassAlias] = new DBObjectSearch($sJoinClass, $sJoinClassAlias);
if (!array_key_exists($sFromClass, $aJoinItems))
{
throw new OqlNormalizeException('Unknown class in join condition (left expression)', $sQuery, 0, $oLeftField->GetPosition(), $sFromClass, array_keys($aJoinItems));
throw new OqlNormalizeException('Unknown class in join condition (left expression)', $sQuery, $oLeftField->GetParentDetails(), array_keys($aJoinItems));
}
if (!array_key_exists($sToClass, $aJoinItems))
{
throw new OqlNormalizeException('Unknown class in join condition (right expression)', $sQuery, 0, $oRightField->GetPosition(), $sToClass, array_keys($aJoinItems));
throw new OqlNormalizeException('Unknown class in join condition (right expression)', $sQuery, $oRightField->GetParentDetails(), array_keys($aJoinItems));
}
$aExtKeys = array_keys(MetaModel::GetExternalKeys($oResultFilter->m_aClasses[$sFromClass]));
$aExtKeys = array_keys(MetaModel::GetExternalKeys($aAliases[$sFromClass]));
if (!in_array($sExtKeyAttCode, $aExtKeys))
{
throw new OqlNormalizeException('Unknown external key in join condition (left expression)', $sQuery, 0, $oLeftField->GetPosition(), $sExtKeyAttCode, $aExtKeys);
throw new OqlNormalizeException('Unknown external key in join condition (left expression)', $sQuery, $oLeftField->GetNameDetails(), $aExtKeys);
}
if ($sFromClass == $sJoinClassAlias)
@@ -847,7 +849,7 @@ class DBObjectSearch
$oConditionTree = $oOqlQuery->GetCondition();
if ($oConditionTree instanceof Expression)
{
$oResultFilter->m_oSearchCondition = $oResultFilter->OQLExpressionToCondition($sQuery, $oConditionTree, $oResultFilter->m_aClasses);
$oResultFilter->m_oSearchCondition = $oResultFilter->OQLExpressionToCondition($sQuery, $oConditionTree, $aAliases);
}
return $oResultFilter;
@@ -934,6 +936,10 @@ class DBObjectSearch
}
}
}
// #@# todo - obsolete smoothly, first give the OQL version !
// throw new CoreException('SibusQL has been obsoleted, please update your queries', array('sibusql'=>$sQuery, 'oql'=>$oFilter->ToOQL()));
return $oFilter;
}

View File

@@ -22,6 +22,8 @@ abstract class Expression
// recursively builds an array of class => fieldname
abstract public function ListRequiredFields();
abstract public function IsTrue();
public function RequiresField($sClass, $sFieldName)
{
// #@# todo - optimize : this is called quite often when building a single query !
@@ -50,6 +52,8 @@ abstract class Expression
public function LogAnd($oExpr)
{
if ($this->IsTrue()) return clone $oExpr;
if ($oExpr->IsTrue()) return clone $this;
return new BinaryExpression($this, 'AND', $oExpr);
}
@@ -57,7 +61,6 @@ abstract class Expression
{
return new BinaryExpression($this, 'OR', $oExpr);
}
}
@@ -90,6 +93,16 @@ class BinaryExpression extends Expression
$this->m_sOperator = $sOperator;
}
public function IsTrue()
{
// return true if we are certain that it will be true
if ($this->m_sOperator == 'AND')
{
if ($this->m_oLeftExpr->IsTrue() && $this->m_oLeftExpr->IsTrue()) return true;
}
return false;
}
public function GetLeftExpr()
{
return $this->m_oLeftExpr;
@@ -139,6 +152,12 @@ class UnaryExpression extends Expression
$this->m_value = $value;
}
public function IsTrue()
{
// return true if we are certain that it will be true
return ($this->m_value == 1);
}
public function GetValue()
{
return $this->m_value;
@@ -179,6 +198,11 @@ class TrueExpression extends ScalarExpression
{
parent::__construct(1);
}
public function IsTrue()
{
return true;
}
}
class FieldExpression extends UnaryExpression
@@ -194,6 +218,12 @@ class FieldExpression extends UnaryExpression
$this->m_sName = $sName;
}
public function IsTrue()
{
// return true if we are certain that it will be true
return false;
}
public function GetParent() {return $this->m_sParent;}
public function GetName() {return $this->m_sName;}
@@ -251,6 +281,12 @@ class ListExpression extends Expression
$this->m_aExpressions = $aExpressions;
}
public function IsTrue()
{
// return true if we are certain that it will be true
return false;
}
public function GetItems()
{
return $this->m_aExpressions;
@@ -300,6 +336,12 @@ class FunctionExpression extends Expression
$this->m_aArgs = $aArgExpressions;
}
public function IsTrue()
{
// return true if we are certain that it will be true
return false;
}
public function GetVerb()
{
return $this->m_sVerb;
@@ -353,6 +395,12 @@ class IntervalExpression extends Expression
$this->m_sUnit = $sUnit;
}
public function IsTrue()
{
// return true if we are certain that it will be true
return false;
}
public function GetValue()
{
return $this->m_oValue;
@@ -389,6 +437,12 @@ class CharConcatExpression extends Expression
$this->m_aExpressions = $aExpressions;
}
public function IsTrue()
{
// return true if we are certain that it will be true
return false;
}
public function GetItems()
{
return $this->m_aExpressions;

View File

@@ -107,10 +107,10 @@ class FilterPrivateKey extends FilterDefinition
{
static protected function ListExpectedParams()
{
return array_merge(parent::ListExpectedParams(), array("pkey_field"));
return array_merge(parent::ListExpectedParams(), array("id_field"));
}
public function GetType() {return "PKey";}
public function GetType() {return "PrivateKey";}
public function GetTypeDesc() {return "Match against object identifier";}
public function GetLabel()
@@ -139,7 +139,7 @@ class FilterPrivateKey extends FilterDefinition
public function GetFilterSQLExpr($sOpCode, $value)
{
$sFieldName = $this->Get("pkey_field");
$sFieldName = $this->Get("id_field");
// #@# not obliged to quote... these are numbers !!!
$sQValue = CMDBSource::Quote($value);
switch($sOpCode)
@@ -162,7 +162,7 @@ class FilterPrivateKey extends FilterDefinition
}
public function TemporaryGetSQLCol()
{
return $this->Get("pkey_field");
return $this->Get("id_field");
}
}

View File

@@ -274,7 +274,7 @@ abstract class MetaModel
final static public function DBGetTable($sClass, $sAttCode = null)
{
self::_check_subclass($sClass);
if (empty($sAttCode) || ($sAttCode == "pkey"))
if (empty($sAttCode) || ($sAttCode == "id"))
{
$sTableRaw = self::$m_aClassParams[$sClass]["db_table"];
if (empty($sTableRaw))
@@ -801,19 +801,19 @@ abstract class MetaModel
}
}
// Add a 'pkey' filter
// Add a 'id' filter
//
if (array_key_exists('pkey', self::$m_aAttribDefs[$sClass]))
if (array_key_exists('id', self::$m_aAttribDefs[$sClass]))
{
trigger_error("Class $sClass, 'pkey' is a reserved keyword, it cannot be used as an attribute code", E_USER_ERROR);
trigger_error("Class $sClass, 'id' is a reserved keyword, it cannot be used as an attribute code", E_USER_ERROR);
}
if (array_key_exists('pkey', self::$m_aFilterDefs[$sClass]))
if (array_key_exists('id', self::$m_aFilterDefs[$sClass]))
{
trigger_error("Class $sClass, 'pkey' is a reserved keyword, it cannot be used as a filter code", E_USER_ERROR);
trigger_error("Class $sClass, 'id' is a reserved keyword, it cannot be used as a filter code", E_USER_ERROR);
}
$oFilter = new FilterPrivateKey('pkey', array('pkey_field' => self::DBGetKey($sClass)));
self::$m_aFilterDefs[$sClass]['pkey'] = $oFilter;
self::$m_aFilterOrigins[$sClass]['pkey'] = $sClass;
$oFilter = new FilterPrivateKey('id', array('id_field' => self::DBGetKey($sClass)));
self::$m_aFilterDefs[$sClass]['id'] = $oFilter;
self::$m_aFilterOrigins[$sClass]['id'] = $sClass;
// Add a 'class' attribute/filter to the root classes and their children
//
@@ -1270,8 +1270,8 @@ abstract class MetaModel
if (empty($aExpectedAtts) && $bIsOnQueriedClass)
{
// default to the whole list of attributes + the very std pkey/finalclass
$aExpectedAtts['pkey'] = 'pkey';
// default to the whole list of attributes + the very std id/finalclass
$aExpectedAtts['id'] = 'id';
foreach (self::GetAttributesList($sClass) as $sAttCode)
{
$aExpectedAtts[$sAttCode] = $sAttCode; // alias == attcode
@@ -1337,7 +1337,6 @@ abstract class MetaModel
// Filter on objects referencing me
foreach ($oFilter->GetCriteria_ReferencedBy() as $sForeignClass => $aKeysAndFilters)
{
$sForeignClassAlias = $oFilter->GetClassAlias();
foreach ($aKeysAndFilters as $sForeignKeyAttCode => $oForeignFilter)
{
$oForeignKeyAttDef = self::GetAttributeDef($sForeignClass, $sForeignKeyAttCode);
@@ -1352,8 +1351,10 @@ abstract class MetaModel
//self::DbgTrace($oSelectForeign->RenderSelect(array()));
$oSelectForeign = self::MakeQuery($sGlobalTargetAlias, $oConditionTree, $aClassAliases, $aTableAliases, $aTranslation, $oForeignFilter, $aExpAtts);
$sForeignKeyField = $oForeignKeyAttDef->GetSQLExpr();
$oSelectBase->AddInnerJoin($oSelectForeign, $sKeyField, $sForeignKeyField);
$sForeignClassAlias = $oForeignFilter->GetClassAlias();
$sForeignKeyTable = $aTranslation[$sForeignClassAlias][$sForeignKeyAttCode][0];
$sForeignKeyColumn = $aTranslation[$sForeignClassAlias][$sForeignKeyAttCode][1];
$oSelectBase->AddInnerJoin($oSelectForeign, $sKeyField, $sForeignKeyColumn, $sForeignKeyTable);
}
}
@@ -1415,7 +1416,7 @@ abstract class MetaModel
$sTargetClass = $oFilter->GetClass();
$sTargetAlias = $oFilter->GetClassAlias();
$sTable = self::DBGetTable($sTableClass);
$sTableAlias = self::GenerateUniqueAlias($aTableAliases, $sTable, $sTable);
$sTableAlias = self::GenerateUniqueAlias($aTableAliases, $sTargetAlias.'_'.$sTable, $sTable);
$bIsOnQueriedClass = ($sTargetAlias == $sGlobalTargetAlias);
@@ -1432,12 +1433,12 @@ abstract class MetaModel
//
if ($bIsOnQueriedClass)
{
$aSelect[$aExpectedAtts['pkey']] = new FieldExpression(self::DBGetKey($sTableClass), $sTableAlias);
$aSelect[$aExpectedAtts['id']] = new FieldExpression(self::DBGetKey($sTableClass), $sTableAlias);
}
// We need one pkey to be the key, let's take the one corresponding to the leaf
if ($sTableClass == $sTargetClass)
{
$aTranslation[$sTargetAlias]['pkey'] = array($sTableAlias, self::DBGetKey($sTableClass));
$aTranslation[$sTargetAlias]['id'] = array($sTableAlias, self::DBGetKey($sTableClass));
}
// 1/b - Get the other attributes
@@ -1481,10 +1482,6 @@ abstract class MetaModel
//
foreach(self::$m_aFilterDefs[$sTargetClass] as $sFltCode => $oFltAtt)
{
// Skip pkey
// no, this is a bug now! ?!?!?
// if ($sFltCode == 'pkey') continue;
// Skip this filter if not defined in this table
if (self::$m_aFilterOrigins[$sTargetClass][$sFltCode] != $sTableClass) continue;
@@ -1579,23 +1576,25 @@ abstract class MetaModel
return $oSelectBase;
}
public static function GenerateUniqueAlias(&$aAliases, $sNewName, $sRealName, $iTentative = 0)
public static function GenerateUniqueAlias(&$aAliases, $sNewName, $sRealName)
{
// Algo: Build an alias, then check it amongst the contents of $aAliases
if ($iTentative == 0) $sProposedAlias = $sNewName;
else $sProposedAlias = $sNewName.$iTentative;
foreach($aAliases as $sAlias=>$sNameWeDontCare)
if (!array_key_exists($sNewName, $aAliases))
{
// If the name is already used, then recursively try to get another one
if ($sProposedAlias == $sAlias) return self::GenerateUniqueAlias($aAliases, $sNewName, $sRealName, $iTentative + 1);
$aAliases[$sNewName] = $sRealName;
return $sNewName;
}
// The proposed alias has been proven to be unique
// Record it and return its value
$aAliases[$sProposedAlias] = $sRealName;
return $sProposedAlias;
for ($i = 1 ; $i < 100 ; $i++)
{
$sAnAlias = $sNewName.$i;
if (!array_key_exists($sAnAlias, $aAliases))
{
// Create that new alias
$aAliases[$sAnAlias] = $sRealName;
return $sAnAlias;
}
}
throw new CoreException('Failed to create an alias', array('aliases' => $aAliases, 'new'=>$sNewName));
}
public static function CheckDefinitions()
@@ -1630,7 +1629,7 @@ abstract class MetaModel
if (empty($sNameAttCode))
{
// let's try this !!!
// $aErrors[$sClass][] = "Missing value for name definition: the framework will (should...) replace it by the pkey";
// $aErrors[$sClass][] = "Missing value for name definition: the framework will (should...) replace it by the id";
// $aSugFix[$sClass][] = "Expecting a value in ".implode(", ", self::GetAttributesList($sClass));
}
else if(!self::IsValidAttCode($sClass, $sNameAttCode))
@@ -2013,7 +2012,7 @@ abstract class MetaModel
{
$sSelWrongRecs .= " AND maintable.`$sKeyField` NOT IN ('".implode("', '", $aPlannedDel[$sTable])."')";
}
$aWrongRecords = CMDBSource::QueryToCol($sSelWrongRecs, "pkey");
$aWrongRecords = CMDBSource::QueryToCol($sSelWrongRecs, "id");
if (count($aWrongRecords) == 0) return;
if (!array_key_exists($sRootClass, $aErrorsAndFixes)) $aErrorsAndFixes[$sRootClass] = array();
@@ -2069,7 +2068,7 @@ abstract class MetaModel
// skip the current table
if ($sFriendTable == $sTable) continue;
$sFindRelatedRec = "SELECT DISTINCT maintable.`$sFriendKey` AS pkey FROM `$sFriendTable` AS maintable WHERE maintable.`$sFriendKey` IN ($sDeleteKeys)";
$sFindRelatedRec = "SELECT DISTINCT maintable.`$sFriendKey` AS id FROM `$sFriendTable` AS maintable WHERE maintable.`$sFriendKey` IN ($sDeleteKeys)";
self::DBCheckIntegrity_Check2Delete($sFindRelatedRec, "Cascading deletion of record in friend table `<em>$sTable</em>`", $sFriendClass, $aErrorsAndFixes, $iNewDelCount, $aPlannedDel, true);
}
}
@@ -2085,7 +2084,7 @@ abstract class MetaModel
{
$sSelWrongRecs .= " AND maintable.`$sKeyField` NOT IN ('".implode("', '", $aPlannedDel[$sTable])."')";
}
$aWrongRecords = CMDBSource::QueryToCol($sSelWrongRecs, "pkey");
$aWrongRecords = CMDBSource::QueryToCol($sSelWrongRecs, "id");
if (count($aWrongRecords) == 0) return;
if (!array_key_exists($sRootClass, $aErrorsAndFixes)) $aErrorsAndFixes[$sRootClass] = array();
@@ -2150,7 +2149,7 @@ abstract class MetaModel
$aAllowedValues = self::EnumChildClasses($sClass, ENUM_CHILD_CLASSES_ALL);
$sAllowedValues = implode(",", CMDBSource::Quote($aAllowedValues, true));
$sSelWrongRecs = "SELECT DISTINCT maintable.`$sKeyField` AS pkey FROM `$sTable` AS maintable WHERE `$sFinalClassField` NOT IN ($sAllowedValues)";
$sSelWrongRecs = "SELECT DISTINCT maintable.`$sKeyField` AS id FROM `$sTable` AS maintable WHERE `$sFinalClassField` NOT IN ($sAllowedValues)";
self::DBCheckIntegrity_Check2Delete($sSelWrongRecs, "final class (field `<em>$sFinalClassField</em>`) is wrong (expected a value in {".$sAllowedValues."})", $sClass, $aErrorsAndFixes, $iNewDelCount, $aPlannedDel);
}
@@ -2168,13 +2167,13 @@ abstract class MetaModel
// Check that any record found here has its counterpart in the root table
// and which refers to a child class
//
$sSelWrongRecs = "SELECT DISTINCT maintable.`$sKeyField` AS pkey FROM `$sTable` as maintable LEFT JOIN `$sRootTable` ON maintable.`$sKeyField` = `$sRootTable`.`$sRootKey` AND `$sRootTable`.`$sFinalClassField` IN ($sExpectedClasses) WHERE `$sRootTable`.`$sRootKey` IS NULL";
$sSelWrongRecs = "SELECT DISTINCT maintable.`$sKeyField` AS id FROM `$sTable` as maintable LEFT JOIN `$sRootTable` ON maintable.`$sKeyField` = `$sRootTable`.`$sRootKey` AND `$sRootTable`.`$sFinalClassField` IN ($sExpectedClasses) WHERE `$sRootTable`.`$sRootKey` IS NULL";
self::DBCheckIntegrity_Check2Delete($sSelWrongRecs, "Found a record in `<em>$sTable</em>`, but no counterpart in root table `<em>$sRootTable</em>` (inc. records pointing to a class in {".$sExpectedClasses."})", $sClass, $aErrorsAndFixes, $iNewDelCount, $aPlannedDel);
// Check that any record found in the root table and referring to a child class
// has its counterpart here (detect orphan nodes -root or in the middle of the hierarchy)
//
$sSelWrongRecs = "SELECT DISTINCT maintable.`$sRootKey` AS pkey FROM `$sRootTable` AS maintable LEFT JOIN `$sTable` ON maintable.`$sRootKey` = `$sTable`.`$sKeyField` WHERE `$sTable`.`$sKeyField` IS NULL AND maintable.`$sFinalClassField` IN ($sExpectedClasses)";
$sSelWrongRecs = "SELECT DISTINCT maintable.`$sRootKey` AS id FROM `$sRootTable` AS maintable LEFT JOIN `$sTable` ON maintable.`$sRootKey` = `$sTable`.`$sKeyField` WHERE `$sTable`.`$sKeyField` IS NULL AND maintable.`$sFinalClassField` IN ($sExpectedClasses)";
self::DBCheckIntegrity_Check2Delete($sSelWrongRecs, "Found a record in root table `<em>$sRootTable</em>`, but no counterpart in table `<em>$sTable</em>` (root records pointing to a class in {".$sExpectedClasses."})", $sRootClass, $aErrorsAndFixes, $iNewDelCount, $aPlannedDel);
}
@@ -2194,7 +2193,7 @@ abstract class MetaModel
$sExtKeyField = $oAttDef->GetSQLExpr();
// Note: a class/table may have an external key on itself
$sSelBase = "SELECT DISTINCT maintable.`$sKeyField` AS pkey, maintable.`$sExtKeyField` AS extkey FROM `$sTable` AS maintable LEFT JOIN `$sRemoteTable` ON maintable.`$sExtKeyField` = `$sRemoteTable`.`$sRemoteKey`";
$sSelBase = "SELECT DISTINCT maintable.`$sKeyField` AS id, maintable.`$sExtKeyField` AS extkey FROM `$sTable` AS maintable LEFT JOIN `$sRemoteTable` ON maintable.`$sExtKeyField` = `$sRemoteTable`.`$sRemoteKey`";
$sSelWrongRecs = $sSelBase." WHERE `$sRemoteTable`.`$sRemoteKey` IS NULL";
if ($oAttDef->IsNullAllowed())
@@ -2239,7 +2238,7 @@ abstract class MetaModel
$sMyAttributeField = $oAttDef->GetSQLExpr();
$sDefaultValue = $oAttDef->GetDefaultValue();
$sSelWrongRecs = "SELECT DISTINCT maintable.`$sKeyField` AS pkey FROM `$sTable` AS maintable WHERE maintable.`$sMyAttributeField` NOT IN ($sExpectedValues)";
$sSelWrongRecs = "SELECT DISTINCT maintable.`$sKeyField` AS id FROM `$sTable` AS maintable WHERE maintable.`$sMyAttributeField` NOT IN ($sExpectedValues)";
self::DBCheckIntegrity_Check2Update($sSelWrongRecs, "Record having a column ('<em>$sAttCode</em>') with an unexpected value", $sMyAttributeField, CMDBSource::Quote($sDefaultValue), $sClass, $aErrorsAndFixes, $iNewDelCount, $aPlannedDel);
}
}
@@ -2464,7 +2463,7 @@ abstract class MetaModel
public static function MakeSingleRow($sClass, $iKey)
{
$oFilter = new DBObjectSearch($sClass);
$oFilter->AddCondition('pkey', $iKey, '=');
$oFilter->AddCondition('id', $iKey, '=');
$sSQL = self::MakeSelectQuery($oFilter);
//echo "$sSQL</br>\n";
@@ -2496,7 +2495,7 @@ abstract class MetaModel
// @#@ possible improvement: check that the class is valid !
$sRootClass = self::GetRootClass($sClass);
$sFinalClassField = self::DBGetClassField($sRootClass);
trigger_error("Empty class name for object $sClass::{$aRow["pkey"]} (root class '$sRootClass', field '{$sFinalClassField}' is empty)", E_USER_ERROR);
trigger_error("Empty class name for object $sClass::{$aRow["id"]} (root class '$sRootClass', field '{$sFinalClassField}' is empty)", E_USER_ERROR);
}
else
{

File diff suppressed because it is too large Load Diff

View File

@@ -41,14 +41,25 @@ join_item(A) ::= JOIN class_name(X) ON join_condition(C).
join_condition(A) ::= field_id(X) EQ field_id(Y). { A = new BinaryOqlExpression(X, '=', Y); }
condition(A) ::= expression(X). { A = X; }
condition(A) ::= expression_prio4(X). { A = X; }
expression(A) ::= PAR_OPEN expression(X) PAR_CLOSE. { A = X; }
expression(A) ::= expression(X) operator(Y) expression(Z). { A = new BinaryOqlExpression(X, Y, Z); }
expression(A) ::= scalar(X). { A=X; }
expression(A) ::= field_id(X). { A = X; }
expression(A) ::= expression(X) list_operator(Y) list(Z). { A = new BinaryOqlExpression(X, Y, Z); }
expression(A) ::= func_name(X) PAR_OPEN arg_list(Y) PAR_CLOSE. { A = new FunctionOqlExpression(X, Y); }
expression_basic(A) ::= scalar(X). { A = X; }
expression_basic(A) ::= field_id(X). { A = X; }
expression_basic(A) ::= func_name(X) PAR_OPEN arg_list(Y) PAR_CLOSE. { A = new FunctionOqlExpression(X, Y); }
expression_basic(A) ::= PAR_OPEN expression_prio4(X) PAR_CLOSE. { A = X; }
expression_basic(A) ::= expression_basic(X) list_operator(Y) list(Z). { A = new BinaryOqlExpression(X, Y, Z); }
expression_prio1(A) ::= expression_basic(X). { A = X; }
expression_prio1(A) ::= expression_prio1(X) operator1(Y) expression_basic(Z). { A = new BinaryOqlExpression(X, Y, Z); }
expression_prio2(A) ::= expression_prio1(X). { A = X; }
expression_prio2(A) ::= expression_prio2(X) operator2(Y) expression_prio1(Z). { A = new BinaryOqlExpression(X, Y, Z); }
expression_prio3(A) ::= expression_prio2(X). { A = X; }
expression_prio3(A) ::= expression_prio3(X) operator3(Y) expression_prio2(Z). { A = new BinaryOqlExpression(X, Y, Z); }
expression_prio4(A) ::= expression_prio3(X). { A = X; }
expression_prio4(A) ::= expression_prio4(X) operator4(Y) expression_prio3(Z). { A = new BinaryOqlExpression(X, Y, Z); }
list(A) ::= PAR_OPEN scalar_list(X) PAR_CLOSE. {
@@ -72,8 +83,8 @@ arg_list(A) ::= arg_list(L) COMA argument(X). {
array_push(L, X);
A = L;
}
argument(A) ::= expression(X). { A = X; }
argument(A) ::= INTERVAL expression(X) interval_unit(Y). { A = new IntervalOqlExpression(X, Y); }
argument(A) ::= expression_prio4(X). { A = X; }
argument(A) ::= INTERVAL expression_prio4(X) interval_unit(Y). { A = new IntervalOqlExpression(X, Y); }
interval_unit(A) ::= F_DAY(X). { A = X; }
interval_unit(A) ::= F_MONTH(X). { A = X; }
@@ -85,40 +96,42 @@ 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) ::= class_name(X) DOT name(Y). { A = new FieldOqlExpression($this->m_iCol, Y, X); }
class_name(A) ::= name(X). {A=X;}
field_id(A) ::= name(X). { A = new FieldOqlExpression(X); }
field_id(A) ::= class_name(X) DOT name(Y). { A = new FieldOqlExpression(Y, X); }
class_name(A) ::= name(X). { A=X; }
name(A) ::= NAME(X). {
if (X[0] == '`')
{
A = substr(X, 1, strlen(X) - 2);
$name = substr(X, 1, strlen(X) - 2);
}
else
{
A = X;
$name = X;
}
A = new OqlName($name, $this->m_iColPrev);
}
num_value(A) ::= NUMVAL(X). {A=X;}
str_value(A) ::= STRVAL(X). {A=stripslashes(substr(X, 1, strlen(X) - 2));}
operator(A) ::= log_operator(X). {A=X;}
operator(A) ::= num_operator(X). {A=X;}
operator(A) ::= str_operator(X). {A=X;}
operator(A) ::= EQ(X). {A=X;}
operator(A) ::= NOT_EQ(X). {A=X;}
log_operator(A) ::= LOG_AND(X). {A=X;}
log_operator(A) ::= LOG_OR(X). {A=X;}
operator1(A) ::= num_operator1(X). {A=X;}
operator2(A) ::= num_operator2(X). {A=X;}
operator2(A) ::= str_operator(X). {A=X;}
operator2(A) ::= EQ(X). {A=X;}
operator2(A) ::= NOT_EQ(X). {A=X;}
operator3(A) ::= LOG_AND(X). {A=X;}
operator4(A) ::= LOG_OR(X). {A=X;}
num_operator(A) ::= GT(X). {A=X;}
num_operator(A) ::= LT(X). {A=X;}
num_operator(A) ::= GE(X). {A=X;}
num_operator(A) ::= LE(X). {A=X;}
num_operator(A) ::= MATH_DIV(X). {A=X;}
num_operator(A) ::= MATH_MULT(X). {A=X;}
num_operator(A) ::= MATH_PLUS(X). {A=X;}
num_operator(A) ::= MATH_MINUS(X). {A=X;}
num_operator1(A) ::= MATH_DIV(X). {A=X;}
num_operator1(A) ::= MATH_MULT(X). {A=X;}
num_operator2(A) ::= MATH_PLUS(X). {A=X;}
num_operator2(A) ::= MATH_MINUS(X). {A=X;}
num_operator2(A) ::= GT(X). {A=X;}
num_operator2(A) ::= LT(X). {A=X;}
num_operator2(A) ::= GE(X). {A=X;}
num_operator2(A) ::= LE(X). {A=X;}
str_operator(A) ::= LIKE(X). {A=X;}
str_operator(A) ::= NOT_LIKE(X). {A=X;}
@@ -175,18 +188,21 @@ class OQLParser extends OQLParserRaw
// Data used when an exception is raised
protected $m_iLine; // still not used
protected $m_iCol;
protected $m_iColPrev; // this is the interesting one, because the parser will reduce on the next token
protected $m_sSourceQuery;
public function __construct($sQuery)
{
$this->m_iLine = 0;
$this->m_iCol = 0;
$this->m_iColPrev = 0;
$this->m_sSourceQuery = $sQuery;
// no constructor - parent::__construct();
}
public function doParse($token, $value, $iCurrPosition = 0)
{
$this->m_iColPrev = $this->m_iCol;
$this->m_iCol = $iCurrPosition;
return parent::DoParse($token, $value);

View File

@@ -11,26 +11,66 @@ class OQLException extends CoreException
$this->m_sUnexpected = $sUnexpected;
$this->m_aExpecting = $aExpecting;
if (is_null($this->m_aExpecting))
if (is_null($this->m_aExpecting) || (count($this->m_aExpecting) == 0))
{
$sMessage = "$sIssue - found '$sUnexpected' at $iCol in '$sInput'";
$sMessage = "$sIssue - found '{$this->m_sUnexpected}' at $iCol in '$sInput'";
}
else
{
$sExpectations = '{'.implode(', ', $aExpecting).'}';
$sMessage = "$sIssue - found '$sUnexpected' at $iCol in '$sInput', expecting $sExpectations";
$sExpectations = '{'.implode(', ', $this->m_aExpecting).'}';
$sSuggest = self::FindClosestString($this->m_sUnexpected, $this->m_aExpecting);
$sMessage = "$sIssue - found '{$this->m_sUnexpected}' at $iCol in '$sInput', expecting $sExpectations, I would suggest to use '$sSuggest'";
}
// make sure everything is assigned properly
parent::__construct($sMessage, 0);
}
public function getHtmlDesc($sHighlightHtmlBegin = '<b>', $sHighlightHtmlEnd = '</b>')
{
$sRet = htmlentities($this->m_MyIssue.", found '".$this->m_sUnexpected."' in: ");
$sRet .= htmlentities(substr($this->m_sInput, 0, $this->m_iCol));
$sRet .= $sHighlightHtmlBegin.htmlentities(substr($this->m_sInput, $this->m_iCol, strlen($this->m_sUnexpected))).$sHighlightHtmlEnd;
$sRet .= htmlentities(substr($this->m_sInput, $this->m_iCol + strlen($this->m_sUnexpected)));
if (!is_null($this->m_aExpecting) && (count($this->m_aExpecting) > 0))
{
$sExpectations = '{'.implode(', ', $this->m_aExpecting).'}';
$sRet .= ", expecting ".htmlentities($sExpectations);
$sSuggest = self::FindClosestString($this->m_sUnexpected, $this->m_aExpecting);
if (strlen($sSuggest) > 0)
{
$sRet .= ", I would suggest to use '$sHighlightHtmlBegin".htmlentities($sSuggest)."$sHighlightHtmlEnd'";
}
}
return $sRet;
}
static protected function FindClosestString($sInput, $aDictionary)
{
// no shortest distance found, yet
$fShortest = -1;
$sRet = '';
// loop through words to find the closest
foreach ($aDictionary as $sSuggestion)
{
// calculate the distance between the input string and the suggested one
$fDist = levenshtein($sInput, $sSuggestion);
if ($fDist == 0)
{
// Exact match
return $sSuggestion;
}
if ($fShortest < 0 || ($fDist < 4 && $fDist <= $fShortest))
{
// set the closest match, and shortest distance
$sRet = $sSuggestion;
$fShortest = $fDist;
}
}
return $sRet;
}
}

View File

@@ -2,6 +2,10 @@
class OqlNormalizeException extends OQLException
{
public function __construct($sIssue, $sInput, OqlName $oName, $aExpecting = null)
{
parent::__construct($sIssue, $sInput, 0, $oName->GetPos(), $oName->GetValue(), $aExpecting);
}
}
class OqlInterpreterException extends OQLException

View File

@@ -1,30 +1,70 @@
<?
// Position a string within an OQL query
// This is a must if we want to be able to pinpoint an error at any stage of the query interpretation
// In particular, the normalization phase requires this
class OqlName
{
protected $m_sValue;
protected $m_iPos;
public function __construct($sValue, $iPos)
{
$this->m_iPos = $iPos;
$this->m_sValue = $sValue;
}
public function GetValue()
{
return $this->m_sValue;
}
public function GetPos()
{
return $this->m_iPos;
}
public function __toString()
{
return $this->m_sValue;
}
}
class OqlJoinSpec
{
protected $m_sClass;
protected $m_sClassAlias;
protected $m_oClass;
protected $m_oClassAlias;
protected $m_oLeftField;
protected $m_oRightField;
protected $m_oNextJoinspec;
public function __construct($sClass, $sClassAlias, BinaryExpression $oExpression)
public function __construct($oClass, $oClassAlias, BinaryExpression $oExpression)
{
$this->m_sClass = $sClass;
$this->m_sClassAlias = $sClassAlias;
$this->m_oClass = $oClass;
$this->m_oClassAlias = $oClassAlias;
$this->m_oLeftField = $oExpression->GetLeftExpr();
$this->m_oRightField = $oExpression->GetRightExpr();
}
public function GetClass()
{
return $this->m_sClass;
return $this->m_oClass->GetValue();
}
public function GetClassAlias()
{
return $this->m_sClassAlias;
return $this->m_oClassAlias->GetValue();
}
public function GetClassDetails()
{
return $this->m_oClass;
}
public function GetClassAliasDetails()
{
return $this->m_oClassAlias;
}
public function GetLeftField()
{
return $this->m_oLeftField;
@@ -45,18 +85,30 @@ class ScalarOqlExpression extends ScalarExpression
class FieldOqlExpression extends FieldExpression
{
protected $m_iPosition; // position in the source string
public function __construct($iPosition, $sName, $sParent = '')
protected $m_oParent;
protected $m_oName;
public function __construct($oName, $oParent = null)
{
$this->m_iPosition = $iPosition;
parent::__construct($sName, $sParent);
if (is_null($oParent))
{
$oParent = new OqlName('', 0);
}
$this->m_oParent = $oParent;
$this->m_oName = $oName;
parent::__construct($oName->GetValue(), $oParent->GetValue());
}
public function GetPosition()
public function GetParentDetails()
{
return $this->m_iPosition;
}
return $this->m_oParent;
}
public function GetNameDetails()
{
return $this->m_oName;
}
}
class ListOqlExpression extends ListExpression
@@ -72,27 +124,37 @@ class IntervalOqlExpression extends IntervalExpression
}
class OqlQuery
{
protected $m_sClass;
protected $m_sClassAlias;
protected $m_oClass;
protected $m_oClassAlias;
protected $m_aJoins; // array of OqlJoinSpec
protected $m_oCondition; // condition tree (expressions)
public function __construct($sClass, $sClassAlias = '', $oCondition = null, $aJoins = null)
public function __construct($oClass, $oClassAlias = '', $oCondition = null, $aJoins = null)
{
$this->m_sClass = $sClass;
$this->m_sClassAlias = $sClassAlias;
$this->m_oClass = $oClass;
$this->m_oClassAlias = $oClassAlias;
$this->m_aJoins = $aJoins;
$this->m_oCondition = $oCondition;
}
public function GetClass()
{
return $this->m_sClass;
return $this->m_oClass->GetValue();
}
public function GetClassAlias()
{
return $this->m_sClassAlias;
return $this->m_oClassAlias->GetValue();
}
public function GetClassDetails()
{
return $this->m_oClass;
}
public function GetClassAliasDetails()
{
return $this->m_oClassAlias;
}
public function GetJoins()
{
return $this->m_aJoins;

View File

@@ -104,8 +104,9 @@ class SQLQuery
$oSQLQuery = $aJoinInfo["select"];
$sLeftField = $aJoinInfo["leftfield"];
$sRightField = $aJoinInfo["rightfield"];
$sRightTableAlias = $aJoinInfo["righttablealias"];
echo "<li>Join '$sJoinType', $sLeftField, $sRightField".$oSQLQuery->DisplayHtml()."</li>\n";
echo "<li>Join '$sJoinType', $sLeftField, $sRightTableAlias.$sRightField".$oSQLQuery->DisplayHtml()."</li>\n";
}
echo "</ul>";
}
@@ -131,28 +132,35 @@ class SQLQuery
$this->m_oConditionExpr->LogAnd($oConditionExpr);
}
private function AddJoin($sJoinType, $oSQLQuery, $sLeftField, $sRightField)
private function AddJoin($sJoinType, $oSQLQuery, $sLeftField, $sRightField, $sRightTableAlias = '')
{
assert((get_class($oSQLQuery) == __CLASS__) || is_subclass_of($oSQLQuery, __CLASS__));
if (!CMDBSource::IsField($this->m_sTable, $sLeftField))
{
trigger_error("Unknown field '$sLeftField' in table '".$this->m_sTable, E_USER_ERROR);
}
if (!CMDBSource::IsField($oSQLQuery->m_sTable, $sRightField))
if (empty($sRightTableAlias))
{
trigger_error("Unknown field '$sRightField' in table '".$oSQLQuery->m_sTable."'", E_USER_ERROR);
$sRightTableAlias = $oSQLQuery->m_sTableAlias;
}
// #@# Could not be verified here because the namespace is unknown - do we need to check it there?
//
// if (!CMDBSource::IsField($sRightTable, $sRightField))
// {
// trigger_error("Unknown field '$sRightField' in table '".$sRightTable."'", E_USER_ERROR);
// }
$this->m_aJoinSelects[] = array(
"jointype" => $sJoinType,
"select" => $oSQLQuery,
"leftfield" => $sLeftField,
"rightfield" => $sRightField
"rightfield" => $sRightField,
"righttablealias" => $sRightTableAlias
);
}
public function AddInnerJoin($oSQLQuery, $sLeftField, $sRightField)
public function AddInnerJoin($oSQLQuery, $sLeftField, $sRightField, $sRigthtTable = '')
{
$this->AddJoin("inner", $oSQLQuery, $sLeftField, $sRightField);
$this->AddJoin("inner", $oSQLQuery, $sLeftField, $sRightField, $sRigthtTable);
}
public function AddLeftJoin($oSQLQuery, $sLeftField, $sRightField)
{
@@ -259,9 +267,9 @@ class SQLQuery
$sFrom .= " ".self::ClauseFrom($aJoinInfo["subfrom"]);
break;
case "inner":
$sFrom .= " INNER JOIN `".$aJoinInfo["tablename"]."` AS `$sTableAlias`";
$sFrom .= " ON ".$aJoinInfo["joincondition"];
$sFrom .= " INNER JOIN (`".$aJoinInfo["tablename"]."` AS `$sTableAlias`";
$sFrom .= " ".self::ClauseFrom($aJoinInfo["subfrom"]);
$sFrom .= ") ON ".$aJoinInfo["joincondition"];
break;
case "left":
$sFrom .= " LEFT JOIN (`".$aJoinInfo["tablename"]."` AS `$sTableAlias`";
@@ -339,7 +347,7 @@ class SQLQuery
return $sTableAlias;
}
private function privRenderSingleTable(&$aFrom, &$aFields, &$aDelTables, &$aSetValues, $sJoinType = "first", $sCallerAlias = "", $sLeftField = "", $sRightField = "")
private function privRenderSingleTable(&$aFrom, &$aFields, &$aDelTables, &$aSetValues, $sJoinType = 'first', $sCallerAlias = '', $sLeftField = '', $sRightField = '', $sRightTableAlias = '')
{
$aActualTableFields = CMDBSource::GetTableFieldsList($this->m_sTable);
@@ -347,7 +355,11 @@ class SQLQuery
// Handle the various kinds of join (or first table in the list)
//
$sJoinCond = "`$sCallerAlias`.`$sLeftField` = `{$this->m_sTableAlias}`.`$sRightField`";
if (empty($sRightTableAlias))
{
$sRightTableAlias = $this->m_sTableAlias;
}
$sJoinCond = "`$sCallerAlias`.`$sLeftField` = `$sRightTableAlias`.`$sRightField`";
switch ($sJoinType)
{
case "first":
@@ -355,6 +367,7 @@ class SQLQuery
break;
case "inner":
case "left":
// table or tablealias ???
$aFrom[$this->m_sTableAlias] = array("jointype"=>$sJoinType, "tablename"=>$this->m_sTable, "joincondition"=>"$sJoinCond");
break;
}
@@ -386,8 +399,9 @@ class SQLQuery
$oRightSelect = $aJoinData["select"];
$sLeftField = $aJoinData["leftfield"];
$sRightField = $aJoinData["rightfield"];
$sRightTableAlias = $aJoinData["righttablealias"];
$sJoinTableAlias = $oRightSelect->privRenderSingleTable($aTempFrom, $aFields, $aDelTables, $aSetValues, $sJoinType, $this->m_sTableAlias, $sLeftField, $sRightField);
$sJoinTableAlias = $oRightSelect->privRenderSingleTable($aTempFrom, $aFields, $aDelTables, $aSetValues, $sJoinType, $this->m_sTableAlias, $sLeftField, $sRightField, $sRightTableAlias);
}
$aFrom[$this->m_sTableAlias]['subfrom'] = $aTempFrom;

View File

@@ -337,15 +337,18 @@ abstract class TestBizModel extends TestHandler
static protected function show_list($oObjectSet)
{
$oObjectSet->Rewind();
$aData = array();
while ($oItem = $oObjectSet->Fetch())
{
$aValues = array();
foreach(MetaModel::GetAttributesList(get_class($oItem)) as $sAttCode)
{
$aValues[] = $oItem->GetAsHTML($sAttCode);
$aValues[$sAttCode] = $oItem->GetAsHTML($sAttCode);
}
echo $oItem->GetKey()." => ".implode(", ", $aValues)."</br>\n";
//echo $oItem->GetKey()." => ".implode(", ", $aValues)."</br>\n";
$aData[] = $aValues;
}
echo MyHelpers::make_table_from_assoc_array($aData);
}
static protected function search_and_show_list(DBObjectSearch $oMyFilter)

View File

@@ -42,7 +42,7 @@ function ShowClass($sClass, $sBaseArgs)
$aProps["Subclasses (children + pure PHP)"] = sexyclasslist(MetaModel::GetSubclasses($sClass), $sBaseArgs);
$aProps["Description"] = MetaModel::GetClassDescription($sClass);
$aProps["Autoincrement pkey?"] = MetaModel::IsAutoIncrementKey($sClass);
$aProps["Autoincrement id?"] = MetaModel::IsAutoIncrementKey($sClass);
$aProps["Key label"] = MetaModel::GetKeyLabel($sClass);
$aProps["Name attribute"] = MetaModel::GetNameAttributeCode($sClass);
$aProps["Reconciliation keys"] = implode(", ", MetaModel::GetReconcKeys($sClass));
@@ -188,7 +188,8 @@ function DebugQuery($sConfigFile)
echo "<h1>Follow up the query build</h1>\n";
MetaModel::StartDebugQuery();
$oFlt = DBObjectSearch::FromSibuSQL($sQuery);
$oFlt = DBObjectSearch::FromOQL($sQuery);
echo "<p>To OQL: ".$oFlt->ToOQL()."</p>";
$sSQL = MetaModel::MakeSelectQuery($oFlt);
MetaModel::StopDebugQuery();

View File

@@ -706,6 +706,7 @@ class TestQueriesOnFarm extends TestBizModel
throw new UnitTestException("The query '$sQuery' was parsed with success, while it shouldn't (?)");
return false;
}
echo "<p>To OQL: ".$oMyFilter->ToOQL()."</p>";
$this->search_and_show_list($oMyFilter);
@@ -721,25 +722,21 @@ class TestQueriesOnFarm extends TestBizModel
$oFilter2 = DBObjectSearch::unserialize($sSerialize);
try
{
$sQuery2 = MetaModel::MakeSelectQuery($oFilter2);
$sQuery2 = MetaModel::MakeSelectQuery($oFilter2);
}
catch (Exception $e)
{
echo "<p>Could not compute the query after unserialize</p>\n";
echo "<p>Query 1: $sQuery1</p>\n";
MyHelpers::var_dump_html($oMyFilter, true);
echo "<p>Query 2: FAILED</p>\n";
MyHelpers::var_dump_html($oFilter2, true);
MyHelpers::var_cmp_html($oMyFilter, $oFilter2);
throw $e;
}
//if ($oFilter2 != $oMyFilter) no, they may differ while the resulting query is the same!
if ($sQuery1 != $sQuery2)
{
echo "<p>serialize/unserialize mismatch :-(</p>\n";
echo "<p>Query 1: $sQuery1</p>\n";
MyHelpers::var_dump_html($oMyFilter, true);
echo "<p>Query 2: $sQuery2</p>\n";
MyHelpers::var_dump_html($oFilter2, true);
MyHelpers::var_cmp_html($sQuery1, $sQuery2);
MyHelpers::var_cmp_html($oMyFilter, $oFilter2);
return false;
}
return true;
@@ -782,10 +779,14 @@ class TestQueriesOnFarm extends TestBizModel
$aQueries = array(
'SELECT Animal' => true,
'SELECT Animal WHERE Animal.pkey = 1' => false,
'SELECT Animal WHERE Animal.id = 1' => true,
'SELECT Aniiimal' => false,
'SELECTe Animal' => false,
'SELECT * FROM Animal' => false,
'SELECT Animal AS zoo WHERE zoo.species = \'human\'' => true,
'SELECT Animal AS zoo WHERE species = \'human\'' => true,
'SELECT Animal AS zoo WHERE espece = \'human\'' => false,
'SELECT Animal AS zoo WHERE zoo.species IN (\'human\', "pig")' => true,
'SELECT Animal AS zoo WHERE CONCATENATION(zoo.species, zoo.sex) LIKE "hum%male"' => false,
'SELECT Animal AS zoo WHERE CONCAT(zoo.species, zoo.sex) LIKE "hum%male"' => true,
@@ -793,6 +794,7 @@ class TestQueriesOnFarm extends TestBizModel
'SELECT Animal AS zoo WHERE zoo.kind = \'human\'' => false,
'SELECT Animal WHERE Animal.species = \'human\' AND Animal.sex = \'female\'' => true,
'SELECT Mammal AS x WHERE (x.species = \'human\' AND x.name LIKE \'ro%\') OR (x.species = \'donkey\' AND x.name LIKE \'po%\')' => true,
'SELECT Mammal AS x WHERE x.species = \'human\' AND x.name LIKE \'ro%\' OR x.species = \'donkey\' AND x.name LIKE \'po%\'' => true,
'SELECT Mammal AS m WHERE MONTH(m.birth) = 7' => true,
'SELECT Mammal AS m WHERE DAY(m.birth) = 19' => true,
'SELECT Mammal AS m WHERE YEAR(m.birth) = 1971' => true,
@@ -802,6 +804,7 @@ class TestQueriesOnFarm extends TestBizModel
'SELECT Mammal AS m WHERE m.name = IF(FLOOR(ROUND(m.height)) > 2, "pomme", "romain")' => true,
'SELECT Mammal AS m WHERE (1 + 2' => false,
'SELECT Mammal AS m WHERE (1 + 2 * 4 / 23) > 0' => true,
'SELECT Mammal AS m WHERE (4 / 23 * 2 + 1) > 0' => true,
'SELECT Mammal AS m WHERE 1/0' => true,
'SELECT Mammal AS m WHERE MONTH(m.birth) = 7' => true,
'SELECT Animal JOIN Group ON Group.leader = Animal.id' => true,
@@ -813,20 +816,30 @@ class TestQueriesOnFarm extends TestBizModel
'SELECT Animal AS A JOIN Group AS G ON A.id = G.leader' => false,
'SELECT Animal AS A JOIN Group AS G ON G.leader = A.id WHERE A.sex=\'male\' OR G.qwerty = 123' => false,
'SELECT Animal AS A JOIN Group AS G ON G.leader = A.id WHERE A.sex=\'male\' OR G.name LIKE "a%"' => true,
'SELECT Animal AS A JOIN Group AS G ON G.leader = A.id WHERE A.id = 1' => true,
'SELECT Animal AS A JOIN Group AS G ON G.leader = A.id WHERE id = 1' => false,
'SELECT Animal AS A JOIN Group AS G ON A.member = G.id' => false,
'SELECT Mammal AS M JOIN Group AS G ON M.member = G.id' => true,
'SELECT Mammal AS M JOIN Group AS G ON A.member = G.id' => false,
'SELECT Mammal AS myAlias JOIN Group AS myAlias ON myAlias.member = myAlias.id' => false,
'SELECT Mammal AS Mammal JOIN Group AS Mammal ON Mammal.member = Mammal.id' => false,
'SELECT Group AS G WHERE G.leader_name LIKE "%"' => true,
'SELECT Group AS G WHERE G.leader_speed < 100000' => true,
'SELECT Mammal AS M JOIN Group AS G ON M.member = G.id WHERE G.leader_name LIKE "%"' => true,
'SELECT Mammal AS M JOIN Group AS G ON M.member = G.id WHERE G.leader_speed < 100000' => true,
'SELECT Mammal AS Child JOIN Mammal AS Dad ON Child.father = Dad.id' => true,
'SELECT Mammal AS Child JOIN Animal AS Dad ON Child.father = Dad.id' => true,
'SELECT Animal AS Child JOIN Mammal AS Dad ON Child.father = Dad.id' => true,
'SELECT Animal AS Child JOIN Animal AS Dad ON Child.father = Dad.id' => true,
'SELECT Animal AS Dad JOIN Animal AS Child ON Child.father = Dad.id' => true,
'SELECT Animal AS Child JOIN Animal AS Dad ON Child.father = Dad.id JOIN Animal AS Mum ON Child.mother = Mum.id' => true,
'SELECT Animal AS Child JOIN Animal AS Dad ON Child.father = Dad.id JOIN Animal AS Mum ON Child.mother = Mum.id WHERE Dad.pkey = 1' => true,
'SELECT Animal AS Child JOIN Animal AS Dad ON Child.father = Dad.id JOIN Animal AS Mum ON Child.mother = Mum.id WHERE Dad.id = 1' => true,
'SELECT Animal AS Child JOIN Animal AS Dad ON Child.father = Dad.id JOIN Animal AS Mum ON Child.mother = Mum.id WHERE Dad.name = \'romanoff\'' => false,
'SELECT Animal AS Child JOIN Mammal AS Dad ON Child.father = Dad.id' => true,
'SELECT Animal AS Child JOIN Mammal AS Dad ON Child.father = Dad.id JOIN Animal AS Mum ON Child.mother = Mum.id WHERE Dad.name = \'romanoff\'' => true,
'SELECT Animal AS Child JOIN Mammal AS Dad ON Child.father = Dad.id JOIN Animal AS Mum ON Child.mother = Mum.id WHERE Dad.name = \'romanoff\' OR Mum.speed = 0' => true,
'SELECT Animal AS Dad JOIN Animal AS Child ON Child.father = Dad.id JOIN Animal AS Mum ON Child.mother = Mum.id' => true,
'SELECT Mammal AS Dad JOIN Mammal AS Child ON Child.father = Dad.id' => true,
'SELECT Mammal AS Dad JOIN Mammal AS Child ON Child.father = Dad.id JOIN Mammal AS Mum ON Child.mother = Mum.id WHERE Dad.name = \'romanoff\' OR Mum.name=\'chloe\' OR Child.name=\'bizounours\'' => true,
);
//$aQueries = array(
// 'SELECT Mammal AS M JOIN Group AS G ON M.member = G.id WHERE G.leader_name LIKE "%"' => true,