Select multiple objects in OQL (beta, for integration within the UI)

SVN:trunk[327]
This commit is contained in:
Romain Quetiez
2010-04-13 09:42:22 +00:00
parent 646ab068da
commit 5d46a94ddf
9 changed files with 3734 additions and 3600 deletions

View File

@@ -40,11 +40,11 @@ abstract class DBObject
private $m_aLoadedAtt = array(); // Compound objects can be partially loaded, array of sAttCode
// Use the MetaModel::NewObject to build an object (do we have to force it?)
public function __construct($aRow = null)
public function __construct($aRow = null, $sClassAlias = '')
{
if (!empty($aRow))
{
$this->FromRow($aRow);
$this->FromRow($aRow, $sClassAlias);
$this->m_bFullyLoaded = $this->IsFullyLoaded();
return;
}
@@ -170,8 +170,14 @@ abstract class DBObject
$this->m_bFullyLoaded = true;
}
protected function FromRow($aRow)
protected function FromRow($aRow, $sClassAlias = '')
{
if (strlen($sClassAlias) == 0)
{
// Default to the current class
$sClassAlias = get_class($this);
}
$this->m_iKey = null;
$this->m_bIsInDB = true;
$this->m_aCurrValues = array();
@@ -180,7 +186,7 @@ abstract class DBObject
// Get the key
//
$sKeyField = "id";
$sKeyField = $sClassAlias."id";
if (!array_key_exists($sKeyField, $aRow))
{
// #@# Bug ?
@@ -211,9 +217,10 @@ abstract class DBObject
// then one column will be found with an empty suffix, the others have a suffix
// Take care: the function isset will return false in case the value is null,
// which is something that could happen on open joins
if (array_key_exists($sAttCode, $aRow))
$sAttRef = $sClassAlias.$sAttCode;
if (array_key_exists($sAttRef, $aRow))
{
$value = $oAttDef->FromSQLToValue($aRow, $sAttCode);
$value = $oAttDef->FromSQLToValue($aRow, $sAttRef);
$this->m_aCurrValues[$sAttCode] = $value;
$this->m_aOrigValues[$sAttCode] = $value;

View File

@@ -43,9 +43,8 @@ define('SIBUSQLTHISREGEXP', "/this\\.(.*)/U");
*/
class DBObjectSearch
{
private $m_sClass;
private $m_sClassAlias;
private $m_aClasses; // queried classes (alias => class name)
private $m_aSelectedClasses; // selected for the output (alias => class name)
private $m_oSearchCondition;
private $m_aParams;
private $m_aFullText;
@@ -59,9 +58,10 @@ class DBObjectSearch
assert('is_string($sClass)');
assert('MetaModel::IsValidClass($sClass)'); // #@# could do better than an assert, or at least give the caller's reference
// => idee d'un assert avec call stack (autre utilisation = echec sur query SQL)
if (empty($sClassAlias)) $sClassAlias = $sClass;
$this->m_sClass = $sClass;
$this->m_sClassAlias = $sClassAlias;
$this->m_aSelectedClasses = array($sClassAlias => $sClass);
$this->m_aClasses = array($sClassAlias => $sClass);
$this->m_oSearchCondition = new TrueExpression;
$this->m_aParams = array();
@@ -71,6 +71,38 @@ class DBObjectSearch
$this->m_aRelatedTo = array();
}
public function GetClassName($sAlias) {return $this->m_aClasses[$sAlias];}
public function GetJoinedClasses() {return $this->m_aClasses;}
public function GetClass()
{
return reset($this->m_aSelectedClasses);
}
public function GetClassAlias()
{
reset($this->m_aSelectedClasses);
return key($this->m_aSelectedClasses);
}
public function SetSelectedClasses($aNewSet)
{
$this->m_aSelectedClasses = array();
foreach ($aNewSet as $sAlias => $sClass)
{
if (!array_key_exists($sAlias, $this->m_aClasses))
{
throw new CoreException('Unexpected class alias', array('alias'=>$sAlias, 'expected'=>$this->m_aClasses));
}
$this->m_aSelectedClasses[$sAlias] = $sClass;
}
}
public function GetSelectedClasses()
{
return $this->m_aSelectedClasses;
}
public function IsAny()
{
// #@# todo - if (!$this->m_oSearchCondition->IsTrue()) return false;
@@ -173,9 +205,9 @@ class DBObjectSearch
}
if (!empty($sConditionDesc))
{
return "Objects of class '$this->m_sClass', $sConditionDesc";
return "Objects of class '".$this->GetClass()."', $sConditionDesc";
}
return "Any object of class '$this->m_sClass'";
return "Any object of class '".$this->GetClass()."'";
}
protected function TransferConditionExpression($oFilter, $aTranslation)
@@ -190,7 +222,6 @@ class DBObjectSearch
{
$this->m_oSearchCondition = new TrueExpression();
// ? 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)
@@ -205,8 +236,8 @@ class DBObjectSearch
// #@# 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);
MyHelpers::CheckKeyInArray('filter code', $sFilterCode, MetaModel::GetClassFilterDefs($this->GetClass()));
$oFilterDef = MetaModel::GetClassFilterDef($this->GetClass(), $sFilterCode);
if (empty($sOpCode))
{
@@ -216,7 +247,7 @@ class DBObjectSearch
// Preserve backward compatibility - quick n'dirty way to change that API semantic
//
$oField = new FieldExpression($sFilterCode, $this->m_sClassAlias);
$oField = new FieldExpression($sFilterCode, $this->GetClassAlias());
switch($sOpCode)
{
case 'SameDay':
@@ -286,14 +317,20 @@ class DBObjectSearch
protected function AddToNameSpace(&$aClassAliases, &$aAliasTranslation)
{
$sOrigAlias = $this->m_sClassAlias;
$sOrigAlias = $this->GetClassAlias();
if (array_key_exists($sOrigAlias, $aClassAliases))
{
$this->m_sClassAlias = MetaModel::GenerateUniqueAlias($aClassAliases, $sOrigAlias, $this->m_sClass);
$sNewAlias = MetaModel::GenerateUniqueAlias($aClassAliases, $sOrigAlias, $this->GetClass());
$this->m_aSelectedClasses[$sNewAlias] = $this->GetClass();
unset($sOrigAlias, $this->m_aSelectedClasses[$sNewAlias]);
// Translate the condition expression with the new alias
$aAliasTranslation[$sOrigAlias]['*'] = $this->m_sClassAlias;
$aAliasTranslation[$sOrigAlias]['*'] = $sNewAlias;
}
// add the alias into the filter aliases list
$aClassAliases[$this->GetClassAlias()] = $this->GetClass();
foreach($this->m_aPointingTo as $sExtKeyAttCode=>$oFilter)
{
$oFilter->AddToNameSpace($aClassAliases, $aAliasTranslation);
@@ -429,12 +466,6 @@ class DBObjectSearch
}
}
public function GetClassName($sAlias) {return $this->m_aClasses[$sAlias];}
public function GetClasses() {return $this->m_aClasses;}
public function GetClass() {return $this->m_sClass;}
public function GetClassAlias() {return $this->m_sClassAlias;}
public function GetCriteria() {return $this->m_oSearchCondition;}
public function GetCriteria_FullText() {return $this->m_aFullText;}
public function GetCriteria_PointingTo($sKeyAttCode = "")
@@ -636,7 +667,10 @@ class DBObjectSearch
$bRetrofitParams = true;
}
$sRes = "SELECT ".$this->GetClass().' AS '.$this->GetClassAlias();
$sSelectedClasses = implode(', ', array_keys($this->m_aSelectedClasses));
$sRes = 'SELECT '.$sSelectedClasses.' FROM';
$sRes .= ' '.$this->GetClass().' AS '.$this->GetClassAlias();
$sRes .= $this->ToOQL_Joins();
$sRes .= " WHERE ".$this->m_oSearchCondition->Render($aParams, $bRetrofitParams);
@@ -912,6 +946,19 @@ class DBObjectSearch
}
}
// Check and prepare the select information
$aSelected = array();
foreach ($oOqlQuery->GetSelectedClasses() as $oClassDetails)
{
$sClassToSelect = $oClassDetails->GetValue();
if (!array_key_exists($sClassToSelect, $aAliases))
{
throw new OqlNormalizeException('Unknown class [alias]', $sQuery, $oClassDetails, array_keys($aAliases));
}
$aSelected[$sClassToSelect] = $aAliases[$sClassToSelect];
}
$oResultFilter->SetSelectedClasses($aSelected);
$oConditionTree = $oOqlQuery->GetCondition();
if ($oConditionTree instanceof Expression)
{

View File

@@ -28,7 +28,7 @@ class DBObjectSet
$this->m_aArgs = $aArgs;
$this->m_bLoaded = false;
$this->m_aData = array();
$this->m_aData = array(); // array of (row => array of (classalias) => object)
$this->m_aId2Row = array();
$this->m_iCurrRow = 0;
}
@@ -143,6 +143,11 @@ class DBObjectSet
return $this->m_oFilter->GetClass();
}
public function GetSelectedClasses()
{
return $this->m_oFilter->GetSelectedClasses();
}
public function GetRootClass()
{
return MetaModel::GetRootClass($this->GetClass());
@@ -156,11 +161,16 @@ class DBObjectSet
$resQuery = CMDBSource::Query($sSQL);
if (!$resQuery) return;
$sClass = $this->m_oFilter->GetClass();
while ($aRow = CMDBSource::FetchArray($resQuery))
{
$sClass = $this->m_oFilter->GetClass();
$oObject = MetaModel::GetObjectByRow($sClass, $aRow);
$this->AddObject($oObject);
$aObjects = array();
foreach ($this->m_oFilter->GetSelectedClasses() as $sClassAlias => $sClass)
{
$oObject = MetaModel::GetObjectByRow($sClass, $aRow, $sClassAlias);
$aObjects[$sClassAlias] = $oObject;
}
$this->AddObjectExtended($aObjects);
}
CMDBSource::FreeResult($resQuery);
@@ -173,7 +183,7 @@ class DBObjectSet
return count($this->m_aData);
}
public function Fetch()
public function Fetch($sClassAlias = '')
{
if (!$this->m_bLoaded) $this->Load();
@@ -181,11 +191,32 @@ class DBObjectSet
{
return null;
}
$oRetObj = $this->m_aData[$this->m_iCurrRow];
if (strlen($sClassAlias) == 0)
{
$sClassAlias = $this->m_oFilter->GetClassAlias();
}
$oRetObj = $this->m_aData[$this->m_iCurrRow][$sClassAlias];
$this->m_iCurrRow++;
return $oRetObj;
}
// Return the whole line if several classes have been specified in the query
//
public function FetchAssoc()
{
if (!$this->m_bLoaded) $this->Load();
if ($this->m_iCurrRow >= count($this->m_aData))
{
return null;
}
$aRetObjects = $this->m_aData[$this->m_iCurrRow];
$this->m_iCurrRow++;
return $aRetObjects;
}
public function Rewind()
{
$this->Seek(0);
@@ -199,25 +230,36 @@ class DBObjectSet
return $this->m_iCurrRow;
}
public function AddObject($oObject)
public function AddObject($oObject, $sClassAlias = '')
{
// ?usefull? if ($oObject->GetClass() != $this->GetClass()) return;
$sObjClass = get_class($oObject);
if (strlen($sClassAlias) == 0)
{
$sClassAlias = $sObjClass;
}
// it is mandatory to avoid duplicates
if (array_key_exists($oObject->GetKey(), $this->m_aId2Row)) return;
// Do not load here, because the load uses that method too
$iNextPos = count($this->m_aData);
$this->m_aData[$iNextPos] = $oObject;
$this->m_aId2Row[$oObject->GetKey()] = $iNextPos;
$this->m_aData[$iNextPos][$sClassAlias] = $oObject;
$this->m_aId2Row[$sObjClass][$oObject->GetKey()] = $iNextPos;
}
public function AddObjectArray($aObjects)
protected function AddObjectExtended($aObjectArray)
{
$iNextPos = count($this->m_aData);
foreach ($aObjectArray as $sClassAlias => $oObject)
{
$this->m_aData[$iNextPos][$sClassAlias] = $oObject;
$this->m_aId2Row[get_class($oObject)][$oObject->GetKey()] = $iNextPos;
}
}
public function AddObjectArray($aObjects, $sClassAlias = '')
{
// #@# todo - add a check on the object class ?
foreach ($aObjects as $oObj)
{
$this->AddObject($oObj);
$this->AddObject($oObj, $sClassAlias);
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -30,20 +30,27 @@ result ::= query(X). { $this->my_result = X; }
result ::= condition(X). { $this->my_result = X; }
query(A) ::= SELECT class_name(X) join_statement(J) where_statement(W). {
A = new OqlObjectQuery(X, X, W, J);
A = new OqlObjectQuery(X, X, W, J, array(X));
}
query(A) ::= SELECT class_name(X) AS_ALIAS class_name(Y) join_statement(J) where_statement(W). {
A = new OqlObjectQuery(X, Y, W, J);
A = new OqlObjectQuery(X, Y, W, J, array(Y));
}
/*
query(A) ::= SELECT field_id(E) FROM class_name(X) join_statement(J) where_statement(W). {
A = new OqlValueSetQuery(E, X, X, W, J);
query(A) ::= SELECT class_list(E) FROM class_name(X) join_statement(J) where_statement(W). {
A = new OqlObjectQuery(X, X, W, J, E);
}
query(A) ::= SELECT field_id(E) FROM class_name(X) AS_ALIAS class_name(Y) join_statement(J) where_statement(W). {
A = new OqlValueSetQuery(E, X, Y, W, J);
query(A) ::= SELECT class_list(E) FROM class_name(X) AS_ALIAS class_name(Y) join_statement(J) where_statement(W). {
A = new OqlObjectQuery(X, Y, W, J, E);
}
class_list(A) ::= class_name(X). {
A = array(X);
}
class_list(A) ::= class_list(L) COMA class_name(X). {
array_push(L, X);
A = L;
}
*/
where_statement(A) ::= WHERE condition(C). { A = C;}
where_statement(A) ::= . { A = null;}

View File

@@ -22,7 +22,8 @@ class OqlInterpreter
$this->m_sQuery = $sQuery;
}
protected function Parse()
// Note: this function is left public for unit test purposes
public function Parse()
{
$oLexer = new OQLLexer($this->m_sQuery);
$oParser = new OQLParser($this->m_sQuery);
@@ -45,18 +46,6 @@ class OqlInterpreter
return $oRes;
}
/*
public function ParseValueSetQuery()
{
$oRes = $this->Parse();
if (!$oRes instanceof OqlValueSetQuery)
{
throw new OqlException('Expecting a value set query', $this->m_sQuery, 0, 0, get_class($oRes), array('OqlValueSetQuery'));
}
return $oRes;
}
*/
public function ParseExpression()
{
$oRes = $this->Parse();

View File

@@ -150,16 +150,22 @@ abstract class OqlQuery
class OqlObjectQuery extends OqlQuery
{
protected $m_aSelect; // array of selected classes
protected $m_oClass;
protected $m_oClassAlias;
public function __construct($oClass, $oClassAlias = '', $oCondition = null, $aJoins = null)
public function __construct($oClass, $oClassAlias, $oCondition = null, $aJoins = null, $aSelect = null)
{
$this->m_aSelect = $aSelect;
$this->m_oClass = $oClass;
$this->m_oClassAlias = $oClassAlias;
parent::__construct($oCondition, $aJoins);
}
public function GetSelectedClasses()
{
return $this->m_aSelect;
}
public function GetClass()
{
return $this->m_oClass->GetValue();
@@ -179,20 +185,4 @@ class OqlObjectQuery extends OqlQuery
}
}
class OqlValueSetQuery extends OqlObjectQuery
{
protected $m_oSelectExpr;
public function __construct($oSelectExpr, $oClass, $oClassAlias = '', $oCondition = null, $aJoins = null)
{
$this->m_oSelectExpr = $oSelectExpr;
parent::__construct($oClass, $oClassAlias, $oCondition, $aJoins);
}
public function GetSelectExpression()
{
return $this->m_oSelectExpr;
}
}
?>

View File

@@ -6,7 +6,7 @@ class TestSQLQuery extends TestScenarioOnDB
static public function GetDescription() {return 'SQLQuery does not depend on the rest of the framework, therefore it makes sense to have a separate test framework for it';}
static public function GetDBHost() {return 'localhost';}
static public function GetDBUser() {return 'RomainDBLogin';}
static public function GetDBUser() {return 'root';}
static public function GetDBPwd() {return '';}
static public function GetDBName() {return 'TestSQLQuery';}
static public function GetDBSubName() {return 'taratata';}
@@ -160,6 +160,18 @@ class TestOQLParser extends TestFunction
"SELECT A JOIN B ON A.myB = B.id WHERE (A.col1 + B.col2) * B.col1 = A.col2" => true,
'SELECT Device AS D_ JOIN Site AS S_ ON D_.site = S_.id WHERE S_.country = "Francia"' => true,
// Several objects in a row...
//
'SELECT A FROM A' => true,
'SELECT A JOIN B ON A.myB = B.id WHERE A.col1 = 2' => true,
'SELECT A FROM A JOIN B ON A.myB = B.id WHERE A.col1 = 2' => true,
'SELECT B FROM A JOIN B ON A.myB = B.id WHERE A.col1 = 2' => true,
'SELECT A,B FROM A JOIN B ON A.myB = B.id WHERE A.col1 = 2' => true,
'SELECT A, B FROM A JOIN B ON A.myB = B.id WHERE A.col1 = 2' => true,
'SELECT B,A FROM A JOIN B ON A.myB = B.id WHERE A.col1 = 2' => true,
'SELECT A, B,C FROM A JOIN B ON A.myB = B.id' => false,
'SELECT C FROM A JOIN B ON A.myB = B.id WHERE A.col1 = 2' => false,
);
$iErrors = 0;
@@ -933,6 +945,11 @@ class TestQueriesOnFarm extends MyFarm
'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,
// Specifying multiple objects
'SELECT Animal FROM Animal' => true,
'SELECT yelele FROM Animal' => false,
'SELECT Animal FROM Animal AS A' => false,
'SELECT A FROM Animal AS A' => true,
);
//$aQueries = array(
// 'SELECT Mammal AS M JOIN Group AS G ON M.member = G.id WHERE G.leader_name LIKE "%"' => true,