diff --git a/core/dbobject.class.php b/core/dbobject.class.php index 7dbc94de9..f272aba72 100644 --- a/core/dbobject.class.php +++ b/core/dbobject.class.php @@ -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; diff --git a/core/dbobjectsearch.class.php b/core/dbobjectsearch.class.php index bec0d57ff..4c3c998a8 100644 --- a/core/dbobjectsearch.class.php +++ b/core/dbobjectsearch.class.php @@ -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) { diff --git a/core/dbobjectset.class.php b/core/dbobjectset.class.php index f4ab4aff1..903663afd 100644 --- a/core/dbobjectset.class.php +++ b/core/dbobjectset.class.php @@ -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); } } diff --git a/core/metamodel.class.php b/core/metamodel.class.php index a1ec24097..672662d54 100644 --- a/core/metamodel.class.php +++ b/core/metamodel.class.php @@ -1,2945 +1,2952 @@ - - * @author Denis Flaven - * @license http://www.opensource.org/licenses/lgpl-license.php LGPL - * @link www.itop.com - * @since 1.0 - * @version 1.1.1.1 $ - */ -abstract class MetaModel -{ - /////////////////////////////////////////////////////////////////////////// - // - // STATIC Members - // - /////////////////////////////////////////////////////////////////////////// - - // Purpose: workaround the following limitation = PHP5 does not allow to know the class (derived from the current one) - // from which a static function is called (__CLASS__ and self are interpreted during parsing) - private static function GetCallersPHPClass($sExpectedFunctionName = null) - { - //var_dump(debug_backtrace()); - $aBacktrace = debug_backtrace(); - // $aBacktrace[0] is where we are - // $aBacktrace[1] is the caller of GetCallersPHPClass - // $aBacktrace[1] is the info we want - if (!empty($sExpectedFunctionName)) - { - assert('$aBacktrace[2]["function"] == $sExpectedFunctionName'); - } - return $aBacktrace[2]["class"]; - } - - // Static init -why and how it works - // - // We found the following limitations: - //- it is not possible to define non scalar constants - //- it is not possible to declare a static variable as '= new myclass()' - // Then we had do propose this model, in which a derived (non abstract) - // class should implement Init(), to call InheritAttributes or AddAttribute. - - private static function _check_subclass($sClass) - { - // See also IsValidClass()... ???? #@# - // class is mandatory - // (it is not possible to guess it when called as myderived::...) - if (!array_key_exists($sClass, self::$m_aClassParams)) - { - throw new CoreException("Unknown class '$sClass', expected a value in {".implode(', ', array_keys(self::$m_aClassParams))."}"); - } - } - - public static function static_var_dump() - { - var_dump(get_class_vars(__CLASS__)); - } - - private static $m_bDebugQuery = false; - private static $m_iStackDepthRef = 0; - - public static function StartDebugQuery() - { - $aBacktrace = debug_backtrace(); - self::$m_iStackDepthRef = count($aBacktrace); - self::$m_bDebugQuery = true; - } - public static function StopDebugQuery() - { - self::$m_bDebugQuery = false; - } - public static function DbgTrace($value) - { - if (!self::$m_bDebugQuery) return; - $aBacktrace = debug_backtrace(); - $iCallStackPos = count($aBacktrace) - self::$m_bDebugQuery; - $sIndent = ""; - for ($i = 0 ; $i < $iCallStackPos ; $i++) - { - $sIndent .= " .-=^=-. "; - } - $aCallers = array(); - foreach($aBacktrace as $aStackInfo) - { - $aCallers[] = $aStackInfo["function"]; - } - $sCallers = "Callstack: ".implode(', ', $aCallers); - $sFunction = "".$aBacktrace[1]["function"].""; - - if (is_string($value)) - { - echo "$sIndent$sFunction: $value
\n"; - } - else if (is_object($value)) - { - echo "$sIndent$sFunction:\n
\n";
-			print_r($value);
-			echo "
\n"; - } - else - { - echo "$sIndent$sFunction: $value
\n"; - } - } - - private static $m_bTraceQueries = true; - private static $m_aQueriesLog = array(); - - - private static $m_sDBName = ""; - private static $m_sTablePrefix = ""; // table prefix for the current application instance (allow several applications on the same DB) - private static $m_Category2Class = array(); - private static $m_aRootClasses = array(); // array of "classname" => "rootclass" - private static $m_aParentClasses = array(); // array of ("classname" => array of "parentclass") - private static $m_aChildClasses = array(); // array of ("classname" => array of "childclass") - - private static $m_aClassParams = array(); // array of ("classname" => array of class information) - - static public function GetParentPersistentClass($sRefClass) - { - $sClass = get_parent_class($sRefClass); - if (!$sClass) return ''; - - if ($sClass == 'DBObject') return ''; // Warning: __CLASS__ is lower case in my version of PHP - - // Note: the UI/business model may implement pure PHP classes (intermediate layers) - if (array_key_exists($sClass, self::$m_aClassParams)) - { - return $sClass; - } - return self::GetParentPersistentClass($sClass); - } - - final static public function GetName($sClass) - { - self::_check_subclass($sClass); - return self::$m_aClassParams[$sClass]["name"]; - } - final static public function GetCategory($sClass) - { - self::_check_subclass($sClass); - return self::$m_aClassParams[$sClass]["category"]; - } - final static public function HasCategory($sClass, $sCategory) - { - self::_check_subclass($sClass); - return (strpos(self::$m_aClassParams[$sClass]["category"], $sCategory) !== false); - } - final static public function GetClassDescription($sClass) - { - self::_check_subclass($sClass); - return self::$m_aClassParams[$sClass]["description"]; - } - final static public function IsAutoIncrementKey($sClass) - { - self::_check_subclass($sClass); - return (self::$m_aClassParams[$sClass]["key_type"] == "autoincrement"); - } - final static public function GetKeyLabel($sClass) - { - self::_check_subclass($sClass); - return self::$m_aClassParams[$sClass]["key_label"]; - } - final static public function GetNameAttributeCode($sClass) - { - self::_check_subclass($sClass); - return self::$m_aClassParams[$sClass]["name_attcode"]; - } - final static public function GetStateAttributeCode($sClass) - { - self::_check_subclass($sClass); - return self::$m_aClassParams[$sClass]["state_attcode"]; - } - final static public function GetDefaultState($sClass) - { - $sDefaultState = ''; - $sStateAttrCode = self::GetStateAttributeCode($sClass); - if (!empty($sStateAttrCode)) - { - $oStateAttrDef = self::GetAttributeDef($sClass, $sStateAttrCode); - $sDefaultState = $oStateAttrDef->GetDefaultValue(); - } - return $sDefaultState; - } - final static public function GetReconcKeys($sClass) - { - self::_check_subclass($sClass); - return self::$m_aClassParams[$sClass]["reconc_keys"]; - } - final static public function GetDisplayTemplate($sClass) - { - self::_check_subclass($sClass); - return self::$m_aClassParams[$sClass]["display_template"]; - } - final static public function GetAttributeOrigin($sClass, $sAttCode) - { - self::_check_subclass($sClass); - return self::$m_aAttribOrigins[$sClass][$sAttCode]; - } - final static public function GetPrequisiteAttributes($sClass, $sAttCode) - { - self::_check_subclass($sClass); - $oAtt = self::GetAttributeDef($sClass, $sAttCode); - // Temporary implementation: later, we might be able to compute - // the dependencies, based on the attributes definition - // (allowed values and default values) - if ($oAtt->IsWritable()) - { - return $oAtt->GetPrerequisiteAttributes(); - } - else - { - return array(); - } - } - // #@# restore to private ? - final static public function DBGetTable($sClass, $sAttCode = null) - { - self::_check_subclass($sClass); - if (empty($sAttCode) || ($sAttCode == "id")) - { - $sTableRaw = self::$m_aClassParams[$sClass]["db_table"]; - if (empty($sTableRaw)) - { - // return an empty string whenever the table is undefined, meaning that there is no table associated to this 'abstract' class - return ''; - } - else - { - return self::$m_sTablePrefix.$sTableRaw; - } - } - // This attribute has been inherited (compound objects) - return self::DBGetTable(self::$m_aAttribOrigins[$sClass][$sAttCode]); - } - - final static protected function DBEnumTables() - { - // This API do not rely on our capability to query the DB and retrieve - // the list of existing tables - // Rather, it uses the list of expected tables, corresponding to the data model - $aTables = array(); - foreach (self::GetClasses() as $sClass) - { - if (self::IsAbstract($sClass)) continue; - $sTable = self::DBGetTable($sClass); - - // Could be completed later with all the classes that are using a given table - if (!array_key_exists($sTable, $aTables)) - { - $aTables[$sTable] = array(); - } - $aTables[$sTable][] = $sClass; - } - return $aTables; - } - - final static public function DBGetKey($sClass) - { - self::_check_subclass($sClass); - return self::$m_aClassParams[$sClass]["db_key_field"]; - } - final static public function DBGetClassField($sClass) - { - self::_check_subclass($sClass); - return self::$m_aClassParams[$sClass]["db_finalclass_field"]; - } - final static public function HasFinalClassField($sClass) - { - self::_check_subclass($sClass); - if (!array_key_exists("db_finalclass_field", self::$m_aClassParams[$sClass])) return false; - return (self::$m_aClassParams[$sClass]["db_finalclass_field"]); - } - final static public function IsStandaloneClass($sClass) - { - self::_check_subclass($sClass); - - $sRootClass = self::GetRootClass($sClass); - return (!self::HasFinalClassField($sRootClass)); - } - final static public function IsParentClass($sParentClass, $sChildClass) - { - self::_check_subclass($sChildClass); - self::_check_subclass($sParentClass); - if (in_array($sParentClass, self::$m_aParentClasses[$sChildClass])) return true; - if ($sChildClass == $sParentClass) return true; - return false; - } - final static public function IsSameFamilyBranch($sClassA, $sClassB) - { - self::_check_subclass($sClassA); - self::_check_subclass($sClassB); - if (in_array($sClassA, self::$m_aParentClasses[$sClassB])) return true; - if (in_array($sClassB, self::$m_aParentClasses[$sClassA])) return true; - if ($sClassA == $sClassB) return true; - return false; - } - final static public function IsSameFamily($sClassA, $sClassB) - { - self::_check_subclass($sClassA); - self::_check_subclass($sClassB); - return (self::GetRootClass($sClassA) == self::GetRootClass($sClassB)); - } - - // Attributes of a given class may contain attributes defined in a parent class - // - Some attributes are a copy of the definition - // - Some attributes correspond to the upper class table definition (compound objects) - // (see also filters definition) - private static $m_aAttribDefs = array(); // array of ("classname" => array of attributes) - private static $m_aAttribOrigins = array(); // array of ("classname" => array of ("attcode"=>"sourceclass")) - private static $m_aExtKeyFriends = array(); // array of ("classname" => array of ("indirect ext key attcode"=> array of ("relative ext field"))) - final static public function ListAttributeDefs($sClass) - { - self::_check_subclass($sClass); - return self::$m_aAttribDefs[$sClass]; - } - - final public static function GetAttributesList($sClass) - { - self::_check_subclass($sClass); - return array_keys(self::$m_aAttribDefs[$sClass]); - } - - final public static function GetFiltersList($sClass) - { - self::_check_subclass($sClass); - return array_keys(self::$m_aFilterDefs[$sClass]); - } - - final public static function GetKeysList($sClass) - { - self::_check_subclass($sClass); - $aExtKeys = array(); - foreach(self::$m_aAttribDefs[$sClass] as $sAttCode => $oAttDef) - { - if ($oAttDef->IsExternalKey()) - { - $aExtKeys[] = $sAttCode; - } - } - return $aExtKeys; - } - - final static public function IsValidKeyAttCode($sClass, $sAttCode) - { - if (!array_key_exists($sClass, self::$m_aAttribDefs)) return false; - if (!array_key_exists($sAttCode, self::$m_aAttribDefs[$sClass])) return false; - return (self::$m_aAttribDefs[$sClass][$sAttCode]->IsExternalKey()); - } - final static public function IsValidAttCode($sClass, $sAttCode) - { - if (!array_key_exists($sClass, self::$m_aAttribDefs)) return false; - return (array_key_exists($sAttCode, self::$m_aAttribDefs[$sClass])); - } - final static public function IsAttributeOrigin($sClass, $sAttCode) - { - return (self::$m_aAttribOrigins[$sClass][$sAttCode] == $sClass); - } - - final static public function IsValidFilterCode($sClass, $sFilterCode) - { - if (!array_key_exists($sClass, self::$m_aFilterDefs)) return false; - return (array_key_exists($sFilterCode, self::$m_aFilterDefs[$sClass])); - } - public static function IsValidClass($sClass) - { - return (array_key_exists($sClass, self::$m_aAttribDefs)); - } - - public static function IsValidObject($oObject) - { - if (!is_object($oObject)) return false; - return (self::IsValidClass(get_class($oObject))); - } - - public static function IsReconcKey($sClass, $sAttCode) - { - return (in_array($sAttCode, self::GetReconcKeys($sClass))); - } - - final static public function GetAttributeDef($sClass, $sAttCode) - { - self::_check_subclass($sClass); - return self::$m_aAttribDefs[$sClass][$sAttCode]; - } - - final static public function GetExternalKeys($sClass) - { - $aExtKeys = array(); - foreach (self::ListAttributeDefs($sClass) as $sAttCode => $oAtt) - { - if ($oAtt->IsExternalKey()) - { - $aExtKeys[$sAttCode] = $oAtt; - } - } - return $aExtKeys; - } - - final static public function GetLinkedSets($sClass) - { - $aLinkedSets = array(); - foreach (self::ListAttributeDefs($sClass) as $sAttCode => $oAtt) - { - if (is_subclass_of($oAtt, 'AttributeLinkedSet')) - { - $aLinkedSets[$sAttCode] = $oAtt; - } - } - return $aLinkedSets; - } - - final static public function GetExternalFields($sClass, $sKeyAttCode) - { - $aExtFields = array(); - foreach (self::ListAttributeDefs($sClass) as $sAttCode => $oAtt) - { - if ($oAtt->IsExternalField() && ($oAtt->GetKeyAttCode() == $sKeyAttCode)) - { - $aExtFields[] = $oAtt; - } - } - return $aExtFields; - } - - final static public function GetExtKeyFriends($sClass, $sExtKeyAttCode) - { - if (array_key_exists($sExtKeyAttCode, self::$m_aExtKeyFriends[$sClass])) - { - return self::$m_aExtKeyFriends[$sClass][$sExtKeyAttCode]; - } - else - { - return array(); - } - } - - public static function GetLabel($sClass, $sAttCode) - { - $oAttDef = self::GetAttributeDef($sClass, $sAttCode); - if ($oAttDef) return $oAttDef->GetLabel(); - return ""; - } - - public static function GetDescription($sClass, $sAttCode) - { - $oAttDef = self::GetAttributeDef($sClass, $sAttCode); - if ($oAttDef) return $oAttDef->GetDescription(); - return ""; - } - - // Filters of a given class may contain filters defined in a parent class - // - Some filters are a copy of the definition - // - Some filters correspond to the upper class table definition (compound objects) - // (see also attributes definition) - private static $m_aFilterDefs = array(); // array of ("classname" => array filterdef) - private static $m_aFilterOrigins = array(); // array of ("classname" => array of ("attcode"=>"sourceclass")) - - public static function GetClassFilterDefs($sClass) - { - self::_check_subclass($sClass); - return self::$m_aFilterDefs[$sClass]; - } - - final static public function GetClassFilterDef($sClass, $sFilterCode) - { - self::_check_subclass($sClass); - return self::$m_aFilterDefs[$sClass][$sFilterCode]; - } - - public static function GetFilterLabel($sClass, $sFilterCode) - { - $oFilter = self::GetClassFilterDef($sClass, $sFilterCode); - if ($oFilter) return $oFilter->GetLabel(); - return ""; - } - - public static function GetFilterDescription($sClass, $sFilterCode) - { - $oFilter = self::GetClassFilterDef($sClass, $sFilterCode); - if ($oFilter) return $oFilter->GetDescription(); - return ""; - } - - // returns an array of opcode=>oplabel (e.g. "differs from") - public static function GetFilterOperators($sClass, $sFilterCode) - { - $oFilter = self::GetClassFilterDef($sClass, $sFilterCode); - if ($oFilter) return $oFilter->GetOperators(); - return array(); - } - - // returns an opcode - public static function GetFilterLooseOperator($sClass, $sFilterCode) - { - $oFilter = self::GetClassFilterDef($sClass, $sFilterCode); - if ($oFilter) return $oFilter->GetLooseOperator(); - return array(); - } - - public static function GetFilterOpDescription($sClass, $sFilterCode, $sOpCode) - { - $oFilter = self::GetClassFilterDef($sClass, $sFilterCode); - if ($oFilter) return $oFilter->GetOpDescription($sOpCode); - return ""; - } - - public static function GetFilterHTMLInput($sFilterCode) - { - return ""; - } - - // Lists of attributes/search filters - // - private static $m_aListInfos = array(); // array of ("listcode" => various info on the list, common to every classes) - private static $m_aListData = array(); // array of ("classname" => array of "listcode" => list) - // list may be an array of attcode / fltcode - // list may be an array of "groupname" => (array of attcode / fltcode) - - public static function EnumZLists() - { - return array_keys(self::$m_aListInfos); - } - - final static public function GetZListInfo($sListCode) - { - return self::$m_aListInfos[$sListCode]; - } - - public static function GetZListItems($sClass, $sListCode) - { - if (array_key_exists($sClass, self::$m_aListData)) - { - if (array_key_exists($sListCode, self::$m_aListData[$sClass])) - { - return self::$m_aListData[$sClass][$sListCode]; - } - } - $sParentClass = self::GetParentPersistentClass($sClass); - if (empty($sParentClass)) return array(); // nothing for the mother of all classes - // Dig recursively - return self::GetZListItems($sParentClass, $sListCode); - } - - public static function IsAttributeInZList($sClass, $sListCode, $sAttCodeOrFltCode, $sGroup = null) - { - $aZList = self::GetZListItems($sClass, $sListCode); - if (!$sGroup) - { - return (in_array($sAttCodeOrFltCode, $aZList)); - } - return (in_array($sAttCodeOrFltCode, $aZList[$sGroup])); - } - - // - // Relations - // - private static $m_aRelationInfos = array(); // array of ("relcode" => various info on the list, common to every classes) - - public static function EnumRelations() - { - return array_keys(self::$m_aRelationInfos); - } - - public static function EnumRelationProperties($sRelCode) - { - MyHelpers::CheckKeyInArray('relation code', $sRelCode, self::$m_aRelationInfos); - return self::$m_aRelationInfos[$sRelCode]; - } - - final static public function GetRelationProperty($sRelCode, $sProperty) - { - MyHelpers::CheckKeyInArray('relation code', $sRelCode, self::$m_aRelationInfos); - MyHelpers::CheckKeyInArray('relation property', $sProperty, self::$m_aRelationInfos[$sRelCode]); - - return self::$m_aRelationInfos[$sRelCode][$sProperty]; - } - - public static function EnumRelationQueries($sClass, $sRelCode) - { - MyHelpers::CheckKeyInArray('relation code', $sRelCode, self::$m_aRelationInfos); - return call_user_func_array(array($sClass, 'GetRelationQueries'), array($sRelCode)); - } - - // - // Object lifecycle model - // - private static $m_aStates = array(); // array of ("classname" => array of "statecode"=>array('label'=>..., 'description'=>..., attribute_inherit=> attribute_list=>...)) - private static $m_aStimuli = array(); // array of ("classname" => array of ("stimuluscode"=>array('label'=>..., 'description'=>...))) - private static $m_aTransitions = array(); // array of ("classname" => array of ("statcode_from"=>array of ("stimuluscode" => array('target_state'=>..., 'actions'=>array of handlers procs, 'user_restriction'=>TBD))) - - public static function EnumStates($sClass) - { - if (array_key_exists($sClass, self::$m_aStates)) - { - return self::$m_aStates[$sClass]; - } - else - { - return array(); - } - } - - public static function EnumStimuli($sClass) - { - if (array_key_exists($sClass, self::$m_aStimuli)) - { - return self::$m_aStimuli[$sClass]; - } - else - { - return array(); - } - } - - public static function EnumTransitions($sClass, $sStateCode) - { - if (array_key_exists($sClass, self::$m_aTransitions)) - { - if (array_key_exists($sStateCode, self::$m_aTransitions[$sClass])) - { - return self::$m_aTransitions[$sClass][$sStateCode]; - } - } - return array(); - } - public static function GetAttributeFlags($sClass, $sState, $sAttCode) - { - $iFlags = 0; // By default (if no life cycle) no flag at all - $sStateAttCode = self::GetStateAttributeCode($sClass); - if (!empty($sStateAttCode)) - { - $aStates = MetaModel::EnumStates($sClass); - if (!array_key_exists($sState, $aStates)) - { - throw new CoreException("Invalid state '$sState' for class '$sClass', expecting a value in {".implode(', ', array_keys($aStates))."}"); - } - $aCurrentState = $aStates[$sState]; - if ( (array_key_exists('attribute_list', $aCurrentState)) && (array_key_exists($sAttCode, $aCurrentState['attribute_list'])) ) - { - $iFlags = $aCurrentState['attribute_list'][$sAttCode]; - } - } - return $iFlags; - } - - // - // Allowed values - // - - public static function GetAllowedValues_att($sClass, $sAttCode, $aArgs = array(), $sBeginsWith = '') - { - $oAttDef = self::GetAttributeDef($sClass, $sAttCode); - return $oAttDef->GetAllowedValues($aArgs, $sBeginsWith); - } - - public static function GetAllowedValues_flt($sClass, $sFltCode, $aArgs = array(), $sBeginsWith = '') - { - $oFltDef = self::GetClassFilterDef($sClass, $sFltCode); - return $oFltDef->GetAllowedValues($aArgs, $sBeginsWith); - } - - // - // Businezz model declaration verbs (should be static) - // - - public static function RegisterZList($sListCode, $aListInfo) - { - // Check mandatory params - $aMandatParams = array( - "description" => "detailed (though one line) description of the list", - "type" => "attributes | filters", - ); - foreach($aMandatParams as $sParamName=>$sParamDesc) - { - if (!array_key_exists($sParamName, $aListInfo)) - { - throw new CoreException("Declaration of list $sListCode - missing parameter $sParamName"); - } - } - - self::$m_aListInfos[$sListCode] = $aListInfo; - } - - public static function RegisterRelation($sRelCode, $aRelationInfo) - { - // Check mandatory params - $aMandatParams = array( - "description" => "detailed (though one line) description of the list", - "verb_down" => "e.g.: 'impacts'", - "verb_up" => "e.g.: 'is impacted by'", - ); - foreach($aMandatParams as $sParamName=>$sParamDesc) - { - if (!array_key_exists($sParamName, $aRelationInfo)) - { - throw new CoreException("Declaration of relation $sRelCode - missing parameter $sParamName"); - } - } - - self::$m_aRelationInfos[$sRelCode] = $aRelationInfo; - } - - // Must be called once and only once... - public static function InitClasses($sTablePrefix) - { - if (count(self::GetClasses()) > 0) - { - throw new CoreException("InitClasses should not be called more than once -skipped"); - return; - } - - self::$m_sTablePrefix = $sTablePrefix; - - foreach(get_declared_classes() as $sPHPClass) { - if (is_subclass_of($sPHPClass, 'DBObject')) - { - if (method_exists($sPHPClass, 'Init')) - { - call_user_func(array($sPHPClass, 'Init')); - } - } - } - foreach (self::GetClasses() as $sClass) - { - // Compute the fields that will be used to display a pointer to another object - // - self::$m_aExtKeyFriends[$sClass] = array(); - foreach (self::$m_aAttribDefs[$sClass] as $sAttCode => $oAttDef) - { - if ($oAttDef->IsExternalKey(EXTKEY_ABSOLUTE)) - { - // oAttDef is either - // - an external KEY / FIELD (direct), - // - an external field pointing to an external KEY / FIELD - // - an external field pointing to an external field pointing to.... - - // Get the real external key attribute - // It will be our reference to determine the other ext fields related to the same ext key - $oFinalKeyAttDef = $oAttDef->GetKeyAttDef(EXTKEY_ABSOLUTE); - - self::$m_aExtKeyFriends[$sClass][$sAttCode] = array(); - foreach (self::GetExternalFields($sClass, $oAttDef->GetKeyAttCode($sAttCode)) as $oExtField) - { - // skip : those extfields will be processed as external keys - if ($oExtField->IsExternalKey(EXTKEY_ABSOLUTE)) continue; - - // Note: I could not compare the objects by the mean of '===' - // because they are copied for the inheritance, and the internal references are NOT updated - if ($oExtField->GetKeyAttDef(EXTKEY_ABSOLUTE) == $oFinalKeyAttDef) - { - self::$m_aExtKeyFriends[$sClass][$sAttCode][$oExtField->GetCode()] = $oExtField; - } - } - } - } - - // Add a 'id' filter - // - if (array_key_exists('id', self::$m_aAttribDefs[$sClass])) - { - throw new CoreException("Class $sClass, 'id' is a reserved keyword, it cannot be used as an attribute code"); - } - if (array_key_exists('id', self::$m_aFilterDefs[$sClass])) - { - throw new CoreException("Class $sClass, 'id' is a reserved keyword, it cannot be used as a filter code"); - } - $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 - // - if (!self::IsStandaloneClass($sClass)) - { - if (array_key_exists('finalclass', self::$m_aAttribDefs[$sClass])) - { - throw new CoreException("Class $sClass, 'finalclass' is a reserved keyword, it cannot be used as an attribute code"); - } - if (array_key_exists('finalclass', self::$m_aFilterDefs[$sClass])) - { - throw new CoreException("Class $sClass, 'finalclass' is a reserved keyword, it cannot be used as a filter code"); - } - $sClassAttCode = 'finalclass'; - $sRootClass = self::GetRootClass($sClass); - $sDbFinalClassField = self::DBGetClassField($sRootClass); - $oClassAtt = new AttributeClass($sClassAttCode, array( - "label"=>"Class", - "description"=>"Real (final) object class", - "class_category"=>null, - "more_values"=>'', - "sql"=>$sDbFinalClassField, - "default_value"=>$sClass, - "is_null_allowed"=>false, - "depends_on"=>array() - )); - self::$m_aAttribDefs[$sClass][$sClassAttCode] = $oClassAtt; - self::$m_aAttribOrigins[$sClass][$sClassAttCode] = $sRootClass; - - $oClassFlt = new FilterFromAttribute($oClassAtt); - self::$m_aFilterDefs[$sClass][$sClassAttCode] = $oClassFlt; - self::$m_aFilterOrigins[$sClass][$sClassAttCode] = self::GetRootClass($sClass); - } - - // Define defaults values for the standard ZLists - // - //foreach (self::$m_aListInfos as $sListCode => $aListConfig) - //{ - // if (!isset(self::$m_aListData[$sClass][$sListCode])) - // { - // $aAllAttributes = array_keys(self::$m_aAttribDefs[$sClass]); - // self::$m_aListData[$sClass][$sListCode] = $aAllAttributes; - // //echo "

$sClass: $sListCode (".count($aAllAttributes)." attributes)

\n"; - // } - //} - } - - } - - // To be overriden, must be called for any object class (optimization) - public static function Init() - { - // In fact it is an ABSTRACT function, but this is not compatible with the fact that it is STATIC (error in E_STRICT interpretation) - } - // To be overloaded by biz model declarations - public static function GetRelationQueries($sRelCode) - { - // In fact it is an ABSTRACT function, but this is not compatible with the fact that it is STATIC (error in E_STRICT interpretation) - return array(); - } - - public static function Init_Params($aParams) - { - // Check mandatory params - $aMandatParams = array( - "category" => "group classes by modules defining their visibility in the UI", - "name" => "internal class name, may be different than the PHP class name", - "description" => "detailed (though one line) description of the class", - "key_type" => "autoincrement | string", - "key_label" => "if set, then display the key as an attribute", - "name_attcode" => "define wich attribute is the class name, may be an inherited attribute", - "state_attcode" => "define wich attribute is representing the state (object lifecycle)", - "reconc_keys" => "define the attributes that will 'almost uniquely' identify an object in batch processes", - "db_table" => "database table", - "db_key_field" => "database field which is the key", - "db_finalclass_field" => "database field wich is the reference to the actual class of the object, considering that this will be a compound class", - ); - - $sClass = self::GetCallersPHPClass("Init"); - if (!array_key_exists("name", $aParams)) - { - throw new CoreException("Declaration of class $sClass: missing name ({$aMandatParams["name"]})"); - } - - foreach($aMandatParams as $sParamName=>$sParamDesc) - { - if (!array_key_exists($sParamName, $aParams)) - { - throw new CoreException("Declaration of class $sClass - missing parameter $sParamName"); - } - } - - $aCategories = explode(',', $aParams['category']); - foreach ($aCategories as $sCategory) - { - self::$m_Category2Class[$sCategory][] = $sClass; - } - self::$m_Category2Class[''][] = $sClass; // all categories, include this one - - - self::$m_aRootClasses[$sClass] = $sClass; // first, let consider that I am the root... updated on inheritance - self::$m_aParentClasses[$sClass] = array(); - self::$m_aChildClasses[$sClass] = array(); - - self::$m_aClassParams[$sClass]= $aParams; - - self::$m_aAttribDefs[$sClass] = array(); - self::$m_aAttribOrigins[$sClass] = array(); - self::$m_aExtKeyFriends[$sClass] = array(); - self::$m_aFilterDefs[$sClass] = array(); - self::$m_aFilterOrigins[$sClass] = array(); - } - - protected static function object_array_mergeclone($aSource1, $aSource2) - { - $aRes = array(); - foreach ($aSource1 as $key=>$object) - { - $aRes[$key] = clone $object; - } - foreach ($aSource2 as $key=>$object) - { - $aRes[$key] = clone $object; - } - return $aRes; - } - - public static function Init_InheritAttributes($sSourceClass = null) - { - $sTargetClass = self::GetCallersPHPClass("Init"); - if (empty($sSourceClass)) - { - // Default: inherit from parent class - $sSourceClass = self::GetParentPersistentClass($sTargetClass); - if (empty($sSourceClass)) return; // no attributes for the mother of all classes - } - if (isset(self::$m_aAttribDefs[$sSourceClass])) - { - if (!isset(self::$m_aAttribDefs[$sTargetClass])) - { - self::$m_aAttribDefs[$sTargetClass] = array(); - self::$m_aAttribOrigins[$sTargetClass] = array(); - } - self::$m_aAttribDefs[$sTargetClass] = self::object_array_mergeclone(self::$m_aAttribDefs[$sTargetClass], self::$m_aAttribDefs[$sSourceClass]); - self::$m_aAttribOrigins[$sTargetClass] = array_merge(self::$m_aAttribOrigins[$sTargetClass], self::$m_aAttribOrigins[$sSourceClass]); - } - // later on, we might consider inheritance in different ways !!! - //if (strlen(self::DBGetTable($sSourceClass)) != 0) - if (self::HasFinalClassField(self::$m_aRootClasses[$sSourceClass])) - { - // Inherit the root class - self::$m_aRootClasses[$sTargetClass] = self::$m_aRootClasses[$sSourceClass]; - } - else - { - // I am a root class, standalone as well ! - // ???? - //self::$m_aRootClasses[$sTargetClass] = $sTargetClass; - } - self::$m_aParentClasses[$sTargetClass] += self::$m_aParentClasses[$sSourceClass]; - self::$m_aParentClasses[$sTargetClass][] = $sSourceClass; - // I am the child of each and every parent... - foreach(self::$m_aParentClasses[$sTargetClass] as $sAncestorClass) - { - self::$m_aChildClasses[$sAncestorClass][] = $sTargetClass; - } - } - public static function Init_OverloadAttributeParams($sAttCode, $aParams) - { - $sTargetClass = self::GetCallersPHPClass("Init"); - - if (!self::IsValidAttCode($sTargetClass, $sAttCode)) - { - throw new CoreException("Could not overload '$sAttCode', expecting a code from {".implode(", ", self::GetAttributesList($sTargetClass))."}"); - } - self::$m_aAttribDefs[$sTargetClass][$sAttCode]->OverloadParams($aParams); - } - public static function Init_AddAttribute(AttributeDefinition $oAtt) - { - $sTargetClass = self::GetCallersPHPClass("Init"); - self::$m_aAttribDefs[$sTargetClass][$oAtt->GetCode()] = $oAtt; - self::$m_aAttribOrigins[$sTargetClass][$oAtt->GetCode()] = $sTargetClass; - // Note: it looks redundant to put targetclass there, but a mix occurs when inheritance is used - - // Specific case of external fields: - // I wanted to simplify the syntax of the declaration of objects in the biz model - // Therefore, the reference to the host class is set there - $oAtt->SetHostClass($sTargetClass); - } - - public static function Init_InheritFilters($sSourceClass = null) - { - $sTargetClass = self::GetCallersPHPClass("Init"); - if (empty($sSourceClass)) - { - // Default: inherit from parent class - $sSourceClass = self::GetParentPersistentClass($sTargetClass); - if (empty($sSourceClass)) return; // no filters for the mother of all classes - } - if (isset(self::$m_aFilterDefs[$sSourceClass])) - { - if (!isset(self::$m_aFilterDefs[$sTargetClass])) - { - self::$m_aFilterDefs[$sTargetClass] = array(); - self::$m_aFilterOrigins[$sTargetClass] = array(); - } - - foreach (self::$m_aFilterDefs[$sSourceClass] as $sFltCode=>$oFilter) - { - if ($oFilter instanceof FilterFromAttribute) - { - // In that case, cloning is not enough: - // we must ensure that we will point to the correct - // attribute definition (in case some properties are overloaded) - $oAttDef1 = $oFilter->__GetRefAttribute(); - $oAttDef2 = self::GetAttributeDef($sTargetClass, $oAttDef1->GetCode()); - $oNewFilter = new FilterFromAttribute($oAttDef2); - } - else - { - $oNewFilter = clone $oFilter; - } - self::$m_aFilterDefs[$sTargetClass][$sFltCode] = $oNewFilter; - } - - self::$m_aFilterOrigins[$sTargetClass] = array_merge(self::$m_aFilterOrigins[$sTargetClass], self::$m_aFilterOrigins[$sSourceClass]); - } - } - - public static function Init_OverloadFilterParams($sFltCode, $aParams) - { - $sTargetClass = self::GetCallersPHPClass("Init"); - - if (!self::IsValidFilterCode($sTargetClass, $sFltCode)) - { - throw new CoreException("Could not overload '$sFltCode', expecting a code from {".implode(", ", self::GetFiltersList($sTargetClass))."}"); - } - self::$m_aFilterDefs[$sTargetClass][$sFltCode]->OverloadParams($aParams); - } - - public static function Init_AddFilter(FilterDefinition $oFilter) - { - $sTargetClass = self::GetCallersPHPClass("Init"); - self::$m_aFilterDefs[$sTargetClass][$oFilter->GetCode()] = $oFilter; - self::$m_aFilterOrigins[$sTargetClass][$oFilter->GetCode()] = $sTargetClass; - // Note: it looks redundant to put targetclass there, but a mix occurs when inheritance is used - } - public static function Init_AddFilterFromAttribute($sAttCode) - { - $sTargetClass = self::GetCallersPHPClass("Init"); - - $oAttDef = self::GetAttributeDef($sTargetClass, $sAttCode); - - $sFilterCode = $sAttCode; - $oNewFilter = new FilterFromAttribute($oAttDef); - self::$m_aFilterDefs[$sTargetClass][$sFilterCode] = $oNewFilter; - - if ($oAttDef->IsExternalField()) - { - $sKeyAttCode = $oAttDef->GetKeyAttCode(); - $oKeyDef = self::GetAttributeDef($sTargetClass, $sKeyAttCode); - self::$m_aFilterOrigins[$sTargetClass][$sFilterCode] = $oKeyDef->GetTargetClass(); - } - else - { - self::$m_aFilterOrigins[$sTargetClass][$sFilterCode] = $sTargetClass; - } - // Note: it looks redundant to put targetclass there, but a mix occurs when inheritance is used - } - - public static function Init_SetZListItems($sListCode, $aItems) - { - MyHelpers::CheckKeyInArray('list code', $sListCode, self::$m_aListInfos); - - $sTargetClass = self::GetCallersPHPClass("Init"); - self::$m_aListData[$sTargetClass][$sListCode] = $aItems; - } - - public static function Init_DefineState($sStateCode, $aStateDef) - { - $sTargetClass = self::GetCallersPHPClass("Init"); - if (is_null($aStateDef['attribute_list'])) $aStateDef['attribute_list'] = array(); - - $sParentState = $aStateDef['attribute_inherit']; - if (!empty($sParentState)) - { - // Inherit from the given state (must be defined !) - $aToInherit = self::$m_aStates[$sTargetClass][$sParentState]; - // The inherited configuration could be overriden - $aStateDef['attribute_list'] = array_merge($aToInherit, $aStateDef['attribute_list']); - } - self::$m_aStates[$sTargetClass][$sStateCode] = $aStateDef; - - // by default, create an empty set of transitions associated to that state - self::$m_aTransitions[$sTargetClass][$sStateCode] = array(); - } - - public static function Init_DefineStimulus($sStimulusCode, $oStimulus) - { - $sTargetClass = self::GetCallersPHPClass("Init"); - self::$m_aStimuli[$sTargetClass][$sStimulusCode] = $oStimulus; - } - - public static function Init_DefineTransition($sStateCode, $sStimulusCode, $aTransitionDef) - { - $sTargetClass = self::GetCallersPHPClass("Init"); - if (is_null($aTransitionDef['actions'])) $aTransitionDef['actions'] = array(); - self::$m_aTransitions[$sTargetClass][$sStateCode][$sStimulusCode] = $aTransitionDef; - } - - public static function Init_InheritLifecycle($sSourceClass = '') - { - $sTargetClass = self::GetCallersPHPClass("Init"); - if (empty($sSourceClass)) - { - // Default: inherit from parent class - $sSourceClass = self::GetParentPersistentClass($sTargetClass); - if (empty($sSourceClass)) return; // no attributes for the mother of all classes - } - - self::$m_aClassParams[$sTargetClass]["state_attcode"] = self::$m_aClassParams[$sSourceClass]["state_attcode"]; - self::$m_aStates[$sTargetClass] = clone self::$m_aStates[$sSourceClass]; - self::$m_aStimuli[$sTargetClass] = clone self::$m_aStimuli[$sSourceClass]; - self::$m_aTransitions[$sTargetClass] = clone self::$m_aTransitions[$sSourceClass]; - } - - // - // Static API - // - - public static function GetRootClass($sClass = null) - { - self::_check_subclass($sClass); - return self::$m_aRootClasses[$sClass]; - } - public static function IsRootClass($sClass) - { - self::_check_subclass($sClass); - return (self::GetRootClass($sClass) == $sClass); - } - public static function EnumParentClasses($sClass) - { - self::_check_subclass($sClass); - return self::$m_aParentClasses[$sClass]; - } - public static function EnumChildClasses($sClass, $iOption = ENUM_CHILD_CLASSES_EXCLUDETOP) - { - self::_check_subclass($sClass); - - $aRes = self::$m_aChildClasses[$sClass]; - if ($iOption != ENUM_CHILD_CLASSES_EXCLUDETOP) - { - // Add it to the list - $aRes[] = $sClass; - } - return $aRes; - } - - public static function EnumCategories() - { - return array_keys(self::$m_Category2Class); - } - - // Note: use EnumChildClasses to take the compound objects into account - public static function GetSubclasses($sClass) - { - self::_check_subclass($sClass); - $aSubClasses = array(); - foreach(get_declared_classes() as $sSubClass) { - if (is_subclass_of($sSubClass, $sClass)) - { - $aSubClasses[] = $sSubClass; - } - } - return $aSubClasses; - } - public static function GetClasses($sCategory = '') - { - if (array_key_exists($sCategory, self::$m_Category2Class)) - { - return self::$m_Category2Class[$sCategory]; - } - - if (count(self::$m_Category2Class) > 0) - { - throw new CoreException("unkown class category '$sCategory', expecting a value in {".implode(', ', array_keys(self::$m_Category2Class))."}"); - } - return array(); - } - - public static function IsAbstract($sClass) - { - if (strlen(self::DBGetTable($sClass)) == 0) return true; - return false; - } - - protected static $m_aQueryStructCache = array(); - - protected static function PrepareQueryArguments($aArgs) - { - // Translate any object into scalars - // - $aScalarArgs = array(); - foreach($aArgs as $sArgName => $value) - { - if (self::IsValidObject($value)) - { - $aScalarArgs = array_merge($aScalarArgs, $value->ToArgs($sArgName)); - } - else - { - $aScalarArgs[$sArgName] = (string) $value; - } - } - // Add standard contextual arguments - // - $aScalarArgs['current_contact_id'] = UserRights::GetContactId(); - - return $aScalarArgs; - } - - public static function MakeSelectQuery(DBObjectSearch $oFilter, $aOrderBy = array(), $aArgs = array()) - { - // Query caching - // - $bQueryCacheEnabled = true; - $aParams = array(); - $sOqlQuery = $oFilter->ToOql($aParams); // Render with arguments in clear - if ($bQueryCacheEnabled) - { - if (array_key_exists($sOqlQuery, self::$m_aQueryStructCache)) - { - // hit! - $oSelect = clone self::$m_aQueryStructCache[$sOqlQuery]; - } - } - - if (!isset($oSelect)) - { - $aTranslation = array(); - $aClassAliases = array(); - $aTableAliases = array(); - $oConditionTree = $oFilter->GetCriteria(); - $oSelect = self::MakeQuery($oFilter->GetClassAlias(), $oConditionTree, $aClassAliases, $aTableAliases, $aTranslation, $oFilter); - - self::$m_aQueryStructCache[$sOqlQuery] = clone $oSelect; - } - - // Check the order by specification - // - foreach ($aOrderBy as $sFieldAlias => $bAscending) - { - MyHelpers::CheckValueInArray('field name in ORDER BY spec', $sFieldAlias, self::GetAttributesList($oFilter->GetClass())); - if (!is_bool($bAscending)) - { - throw new CoreException("Wrong direction in ORDER BY spec, found '$bAscending' and expecting a boolean value"); - } - } - if (empty($aOrderBy)) - { - $sNameAttCode = self::GetNameAttributeCode($oFilter->GetClass()); - if (!empty($sNameAttCode)) - { - // By default, simply order on the "name" attribute, ascending - $aOrderBy = array($sNameAttCode => true); - } - } - - // Go - // - $aScalarArgs = array_merge(self::PrepareQueryArguments($aArgs), $oFilter->GetInternalParams()); - - try - { - $sRes = $oSelect->RenderSelect($aOrderBy, $aScalarArgs); - } - catch (MissingQueryArgument $e) - { - // Add some information... - $e->addInfo('OQL', $sOqlQuery); - throw $e; - } - - if (self::$m_bTraceQueries) - { - $aParams = array(); - if (!array_key_exists($sOqlQuery, self::$m_aQueriesLog)) - { - self::$m_aQueriesLog[$sOqlQuery] = array( - 'sql' => array(), - 'count' => 0, - ); - } - self::$m_aQueriesLog[$sOqlQuery]['count']++; - self::$m_aQueriesLog[$sOqlQuery]['sql'][] = $sRes; - } - - return $sRes; - } - - public static function ShowQueryTrace() - { - $iTotal = 0; - foreach (self::$m_aQueriesLog as $sOql => $aQueryData) - { - echo "

$sOql

\n"; - $iTotal += $aQueryData['count']; - echo '

'.$aQueryData['count'].'

'; - echo '

Example: '.$aQueryData['sql'][0].'

'; - } - echo "

Total

\n"; - echo "

Count of executed queries: $iTotal

"; - echo "

Count of built queries: ".count(self::$m_aQueriesLog)."

"; - } - - public static function MakeDeleteQuery(DBObjectSearch $oFilter, $aArgs = array()) - { - $aTranslation = array(); - $aClassAliases = array(); - $aTableAliases = array(); - $oConditionTree = $oFilter->GetCriteria(); - $oSelect = self::MakeQuery($oFilter->GetClassAlias(), $oConditionTree, $aClassAliases, $aTableAliases, $aTranslation, $oFilter); - $aScalarArgs = array_merge(self::PrepareQueryArguments($aArgs), $oFilter->GetInternalParams()); - return $oSelect->RenderDelete($aScalarArgs); - } - - public static function MakeUpdateQuery(DBObjectSearch $oFilter, $aValues, $aArgs = array()) - { - // $aValues is an array of $sAttCode => $value - $aTranslation = array(); - $aClassAliases = array(); - $aTableAliases = array(); - $oConditionTree = $oFilter->GetCriteria(); - $oSelect = self::MakeQuery($oFilter->GetClassAlias(), $oConditionTree, $aClassAliases, $aTableAliases, $aTranslation, $oFilter, array(), $aValues); - $aScalarArgs = array_merge(self::PrepareQueryArguments($aArgs), $oFilter->GetInternalParams()); - return $oSelect->RenderUpdate($aScalarArgs); - } - - private static function MakeQuery($sGlobalTargetAlias, &$oConditionTree, &$aClassAliases, &$aTableAliases, &$aTranslation, DBObjectSearch $oFilter, $aExpectedAtts = array(), $aValues = array()) - { - // Note: query class might be different than the class of the filter - // -> this occurs when we are linking our class to an external class (referenced by, or pointing to) - // $aExpectedAtts is an array of sAttCode=>sAlias - $sClass = $oFilter->GetClass(); - $sClassAlias = $oFilter->GetClassAlias(); - - $bIsOnQueriedClass = ($sClassAlias == $sGlobalTargetAlias); - if ($bIsOnQueriedClass) - { - $aClassAliases = array_merge($aClassAliases, $oFilter->GetClasses()); - } - - self::DbgTrace("Entering: ".$oFilter->ToSibuSQL().", ".($bIsOnQueriedClass ? "MAIN" : "SECONDARY").", expectedatts=".count($aExpectedAtts).": ".implode(",", $aExpectedAtts)); - - $sRootClass = self::GetRootClass($sClass); - $sKeyField = self::DBGetKey($sClass); - - if (empty($aExpectedAtts) && $bIsOnQueriedClass) - { - // 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 - } - } - - // Compute a clear view of external keys, and external attributes - // Build the list of external keys: - // -> ext keys required by a closed join ??? - // -> ext keys mentionned in a 'pointing to' condition - // -> ext keys required for an external field - // - $aExtKeys = array(); // array of sTableClass => array of (sAttCode (keys) => array of (sAttCode (fields)=> oAttDef)) - // - // Optimization: could be computed once for all (cached) - // Could be done in MakeQuerySingleTable ??? - // - - if ($bIsOnQueriedClass) - { - // Get all Ext keys for the queried class (??) - foreach(self::GetKeysList($sClass) as $sKeyAttCode) - { - $sKeyTableClass = self::$m_aAttribOrigins[$sClass][$sKeyAttCode]; - $aExtKeys[$sKeyTableClass][$sKeyAttCode] = array(); - } - } - // Get all Ext keys used by the filter - foreach ($oFilter->GetCriteria_PointingTo() as $sKeyAttCode => $trash) - { - $sKeyTableClass = self::$m_aAttribOrigins[$sClass][$sKeyAttCode]; - $aExtKeys[$sKeyTableClass][$sKeyAttCode] = array(); - } - // Add the ext fields used in the select (eventually adds an external key) - foreach(self::ListAttributeDefs($sClass) as $sAttCode=>$oAttDef) - { - if ($oAttDef->IsExternalField()) - { - $sKeyAttCode = $oAttDef->GetKeyAttCode(); - if (array_key_exists($sAttCode, $aExpectedAtts) || $oConditionTree->RequiresField($sClassAlias, $sAttCode)) - { - // Add the external attribute - $sKeyTableClass = self::$m_aAttribOrigins[$sClass][$sKeyAttCode]; - $aExtKeys[$sKeyTableClass][$sKeyAttCode][$sAttCode] = $oAttDef; - } - } - } - - // First query built upon on the leaf (ie current) class - // - self::DbgTrace("Main (=leaf) class, call MakeQuerySingleTable()"); - $oSelectBase = self::MakeQuerySingleTable($sGlobalTargetAlias, $oConditionTree, $aClassAliases, $aTableAliases, $aTranslation, $oFilter, $sClass, $aExpectedAtts, $aExtKeys, $aValues); - - // Then we join the queries of the eventual parent classes (compound model) - foreach(self::EnumParentClasses($sClass) as $sParentClass) - { - if (self::DBGetTable($sParentClass) == "") continue; - self::DbgTrace("Parent class: $sParentClass... let's call MakeQuerySingleTable()"); - $oSelectParentTable = self::MakeQuerySingleTable($sGlobalTargetAlias, $oConditionTree, $aClassAliases, $aTableAliases, $aTranslation, $oFilter, $sParentClass, $aExpectedAtts, $aExtKeys, $aValues); - $oSelectBase->AddInnerJoin($oSelectParentTable, $sKeyField, self::DBGetKey($sParentClass)); - } - - // Filter on objects referencing me - foreach ($oFilter->GetCriteria_ReferencedBy() as $sForeignClass => $aKeysAndFilters) - { - foreach ($aKeysAndFilters as $sForeignKeyAttCode => $oForeignFilter) - { - $oForeignKeyAttDef = self::GetAttributeDef($sForeignClass, $sForeignKeyAttCode); - - // We don't want any attribute from the foreign class, just filter on an inner join - $aExpAtts = array(); - - self::DbgTrace("Referenced by foreign key: $sForeignKeyAttCode... let's call MakeQuery()"); - //self::DbgTrace($oForeignFilter); - //self::DbgTrace($oForeignFilter->ToSibuSQL()); - //self::DbgTrace($oSelectForeign); - //self::DbgTrace($oSelectForeign->RenderSelect(array())); - $oSelectForeign = self::MakeQuery($sGlobalTargetAlias, $oConditionTree, $aClassAliases, $aTableAliases, $aTranslation, $oForeignFilter, $aExpAtts); - - $sForeignClassAlias = $oForeignFilter->GetClassAlias(); - $sForeignKeyTable = $aTranslation[$sForeignClassAlias][$sForeignKeyAttCode][0]; - $sForeignKeyColumn = $aTranslation[$sForeignClassAlias][$sForeignKeyAttCode][1]; - $oSelectBase->AddInnerJoin($oSelectForeign, $sKeyField, $sForeignKeyColumn, $sForeignKeyTable); - } - } - - // Filter on related objects - // - foreach ($oFilter->GetCriteria_RelatedTo() as $aCritInfo) - { - $oSubFilter = $aCritInfo['flt']; - $sRelCode = $aCritInfo['relcode']; - $iMaxDepth = $aCritInfo['maxdepth']; - - // Get the starting point objects - $oStartSet = new CMDBObjectSet($oSubFilter); - - // Get the objects related to those objects... recursively... - $aRelatedObjs = $oStartSet->GetRelatedObjects($sRelCode, $iMaxDepth); - $aRestriction = array_key_exists($sRootClass, $aRelatedObjs) ? $aRelatedObjs[$sRootClass] : array(); - - // #@# todo - related objects and expressions... - // Create condition - if (count($aRestriction) > 0) - { - $oSelectBase->AddCondition($sKeyField.' IN ('.implode(', ', CMDBSource::Quote(array_keys($aRestriction), true)).')'); - } - else - { - // Quick N'dirty -> generate an empty set - $oSelectBase->AddCondition('false'); - } - } - - // Translate the conditions... and go - // - if ($bIsOnQueriedClass) - { - $oConditionTranslated = $oConditionTree->Translate($aTranslation); - $oSelectBase->SetCondition($oConditionTranslated); - } - - // That's all... cross fingers and we'll get some working query - - //MyHelpers::var_dump_html($oSelectBase, true); - //MyHelpers::var_dump_html($oSelectBase->RenderSelect(), true); - if (self::$m_bDebugQuery) $oSelectBase->DisplayHtml(); - return $oSelectBase; - } - - protected static function MakeQuerySingleTable($sGlobalTargetAlias, &$oConditionTree, &$aClassAliases, &$aTableAliases, &$aTranslation, $oFilter, $sTableClass, $aExpectedAtts, $aExtKeys, $aValues) - { - // $aExpectedAtts is an array of sAttCode=>sAlias - // $aExtKeys is an array of sTableClass => array of (sAttCode (keys) => array of sAttCode (fields)) - - // Prepare the query for a single table (compound objects) - // Ignores the items (attributes/filters) that are not on the target table - // Perform an (inner or left) join for every external key (and specify the expected fields) - // - // Returns an SQLQuery - // - $sTargetClass = $oFilter->GetClass(); - $sTargetAlias = $oFilter->GetClassAlias(); - $sTable = self::DBGetTable($sTableClass); - $sTableAlias = self::GenerateUniqueAlias($aTableAliases, $sTargetAlias.'_'.$sTable, $sTable); - - $bIsOnQueriedClass = ($sTargetAlias == $sGlobalTargetAlias); - - self::DbgTrace("Entering: tableclass=$sTableClass, filter=".$oFilter->ToSibuSQL().", ".($bIsOnQueriedClass ? "MAIN" : "SECONDARY").", expectedatts=".count($aExpectedAtts).": ".implode(",", $aExpectedAtts)); - - // 1 - SELECT and UPDATE - // - // Note: no need for any values nor fields for foreign Classes (ie not the queried Class) - // - $aSelect = array(); - $aUpdateValues = array(); - - // 1/a - Get the key - // - if ($bIsOnQueriedClass) - { - $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]['id'] = array($sTableAlias, self::DBGetKey($sTableClass)); - } - - // 1/b - Get the other attributes - // - foreach(self::ListAttributeDefs($sTableClass) as $sAttCode=>$oAttDef) - { - // Skip this attribute if not defined in this table - if (self::$m_aAttribOrigins[$sTargetClass][$sAttCode] != $sTableClass) continue; - - // Skip this attribute if not writable (means that it does not correspond - if (count($oAttDef->GetSQLExpressions()) == 0) continue; - - // Update... - // - if ($bIsOnQueriedClass && array_key_exists($sAttCode, $aValues)) - { - assert ($oAttDef->IsDirectField()); - foreach ($oAttDef->GetSQLValues($aValues[$sAttCode]) as $sColumn => $sValue) - { - $aUpdateValues[$sColumn] = $sValue; - } - } - - // Select... - // - // Skip, if a list of fields has been specified and it is not there - if (!array_key_exists($sAttCode, $aExpectedAtts)) continue; - $sAttAlias = $aExpectedAtts[$sAttCode]; - - if ($oAttDef->IsExternalField()) - { - // skip, this will be handled in the joined tables - } - else - { - // standard field, or external key - // add it to the output - foreach ($oAttDef->GetSQLExpressions() as $sColId => $sSQLExpr) - { - $aSelect[$sAttAlias.$sColId] = new FieldExpression($sSQLExpr, $sTableAlias); - } - } - } - - // 2 - WHERE - // - foreach(self::$m_aFilterDefs[$sTargetClass] as $sFltCode => $oFltAtt) - { - // Skip this filter if not defined in this table - if (self::$m_aFilterOrigins[$sTargetClass][$sFltCode] != $sTableClass) continue; - - // #@# todo - aller plus loin... a savoir que la table de translation doit contenir une "Expression" - foreach($oFltAtt->GetSQLExpressions() as $sColID => $sFltExpr) - { - // Note: I did not test it with filters relying on several expressions... - // as long as sColdID is empty, this is working, otherwise... ? - $aTranslation[$sTargetAlias][$sFltCode.$sColID] = array($sTableAlias, $sFltExpr); - } - } - - // #@# todo - See what a full text search condition should be - // 2' - WHERE / Full text search condition - // - if ($bIsOnQueriedClass) - { - $aFullText = $oFilter->GetCriteria_FullText(); - } - else - { - // Pourquoi ??? - $aFullText = array(); - } - - // 3 - The whole stuff, for this table only - // - $oSelectBase = new SQLQuery($sTable, $sTableAlias, $aSelect, null, $aFullText, $bIsOnQueriedClass, $aUpdateValues); - - // 4 - The external keys -> joins... - // - if (array_key_exists($sTableClass, $aExtKeys)) - { - foreach ($aExtKeys[$sTableClass] as $sKeyAttCode => $aExtFields) - { - $oKeyAttDef = self::GetAttributeDef($sTargetClass, $sKeyAttCode); - - $oExtFilter = $oFilter->GetCriteria_PointingTo($sKeyAttCode); - - // In case the join was not explicitely defined in the filter, - // we need to do it now - if (empty($oExtFilter)) - { - $sKeyClass = $oKeyAttDef->GetTargetClass(); - $sKeyClassAlias = self::GenerateUniqueAlias($aClassAliases, $sKeyClass.'_'.$sKeyAttCode, $sKeyClass); - $oExtFilter = new DBObjectSearch($sKeyClass, $sKeyClassAlias); - } - else - { - // The aliases should not conflict because normalization occured while building the filter - $sKeyClass = $oExtFilter->GetClass(); - $sKeyClassAlias = $oExtFilter->GetClassAlias(); - - // Note: there is no search condition in $oExtFilter, because normalization did merge the condition onto the top of the filter tree - } - - // Specify expected attributes for the target class query - // ... and use the current alias ! - $aExpAtts = array(); - $aIntermediateTranslation = array(); - foreach($aExtFields as $sAttCode => $oAtt) - { - - $sExtAttCode = $oAtt->GetExtAttCode(); - if (array_key_exists($sAttCode, $aExpectedAtts)) - { - // Request this attribute... transmit the alias ! - $aExpAtts[$sExtAttCode] = $aExpectedAtts[$sAttCode]; - } - // Translate mainclass.extfield => remoteclassalias.remotefieldcode - $oRemoteAttDef = self::GetAttributeDef($sKeyClass, $sExtAttCode); - foreach ($oRemoteAttDef->GetSQLExpressions() as $sColID => $sRemoteAttExpr) - { - $aIntermediateTranslation[$sTargetAlias.$sColID][$sAttCode] = array($sKeyClassAlias, $sRemoteAttExpr); - } - //#@# debug - echo "

$sTargetAlias.$sAttCode to $sKeyClassAlias.$sRemoteAttExpr (class: $sKeyClass)

\n"; - } - $oConditionTree = $oConditionTree->Translate($aIntermediateTranslation, false); - - self::DbgTrace("External key $sKeyAttCode (class: $sKeyClass), call MakeQuery()"); - $oSelectExtKey = self::MakeQuery($sGlobalTargetAlias, $oConditionTree, $aClassAliases, $aTableAliases, $aTranslation, $oExtFilter, $aExpAtts); - - $sLocalKeyField = current($oKeyAttDef->GetSQLExpressions()); // get the first column for an external key - $sExternalKeyField = self::DBGetKey($sKeyClass); - self::DbgTrace("External key $sKeyAttCode, Join on $sLocalKeyField = $sExternalKeyField"); - if ($oKeyAttDef->IsNullAllowed()) - { - $oSelectBase->AddLeftJoin($oSelectExtKey, $sLocalKeyField, $sExternalKeyField); - } - else - { - $oSelectBase->AddInnerJoin($oSelectExtKey, $sLocalKeyField, $sExternalKeyField); - } - } - } - - //MyHelpers::var_dump_html($oSelectBase->RenderSelect()); - return $oSelectBase; - } - - public static function GenerateUniqueAlias(&$aAliases, $sNewName, $sRealName) - { - if (!array_key_exists($sNewName, $aAliases)) - { - $aAliases[$sNewName] = $sRealName; - return $sNewName; - } - - 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() - { - if (count(self::GetClasses()) == 0) - { - throw new CoreException("MetaModel::InitClasses() has not been called, or no class has been declared ?!?!"); - } - - $aErrors = array(); - $aSugFix = array(); - foreach (self::GetClasses() as $sClass) - { - if (self::IsAbstract($sClass)) continue; - - $sNameAttCode = self::GetNameAttributeCode($sClass); - if (empty($sNameAttCode)) - { - // let's try this !!! - // $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)) - { - $aErrors[$sClass][] = "Unkown attribute code '".$sNameAttCode."' for the name definition"; - $aSugFix[$sClass][] = "Expecting a value in ".implode(", ", self::GetAttributesList($sClass)); - } - - foreach(self::GetReconcKeys($sClass) as $sReconcKeyAttCode) - if (!empty($sReconcKeyAttCode) && !self::IsValidAttCode($sClass, $sReconcKeyAttCode)) - { - $aErrors[$sClass][] = "Unkown attribute code '".$sReconcKeyAttCode."' in the list of reconciliation keys"; - $aSugFix[$sClass][] = "Expecting a value in ".implode(", ", self::GetAttributesList($sClass)); - } - - foreach(self::ListAttributeDefs($sClass) as $sAttCode=>$oAttDef) - { - // It makes no sense to check the attributes again and again in the subclasses - if (self::$m_aAttribOrigins[$sClass][$sAttCode] != $sClass) continue; - - if ($oAttDef->IsExternalKey()) - { - if (!self::IsValidClass($oAttDef->GetTargetClass())) - { - $aErrors[$sClass][] = "Unkown class '".$oAttDef->GetTargetClass()."' for the external key '$sAttCode'"; - $aSugFix[$sClass][] = "Expecting a value in {".implode(", ", self::GetClasses())."}"; - } - } - elseif ($oAttDef->IsExternalField()) - { - $sKeyAttCode = $oAttDef->GetKeyAttCode(); - if (!self::IsValidAttCode($sClass, $sKeyAttCode) || !self::IsValidKeyAttCode($sClass, $sKeyAttCode)) - { - $aErrors[$sClass][] = "Unkown key attribute code '".$sKeyAttCode."' for the external field $sAttCode"; - $aSugFix[$sClass][] = "Expecting a value in {".implode(", ", self::GetKeysList($sClass))."}"; - } - else - { - $oKeyAttDef = self::GetAttributeDef($sClass, $sKeyAttCode); - $sTargetClass = $oKeyAttDef->GetTargetClass(); - $sExtAttCode = $oAttDef->GetExtAttCode(); - if (!self::IsValidAttCode($sTargetClass, $sExtAttCode)) - { - $aErrors[$sClass][] = "Unkown key attribute code '".$sExtAttCode."' for the external field $sAttCode"; - $aSugFix[$sClass][] = "Expecting a value in {".implode(", ", self::GetKeysList($sTargetClass))."}"; - } - } - } - else // standard attributes - { - // Check that the default values definition is a valid object! - $oValSetDef = $oAttDef->GetValuesDef(); - if (!is_null($oValSetDef) && !$oValSetDef instanceof ValueSetDefinition) - { - $aErrors[$sClass][] = "Allowed values for attribute $sAttCode is not of the relevant type"; - $aSugFix[$sClass][] = "Please set it as an instance of a ValueSetDefinition object."; - } - else - { - // Default value must be listed in the allowed values (if defined) - $aAllowedValues = self::GetAllowedValues_att($sClass, $sAttCode); - if (!is_null($aAllowedValues)) - { - $sDefaultValue = $oAttDef->GetDefaultValue(); - if (!array_key_exists($sDefaultValue, $aAllowedValues)) - { - $aErrors[$sClass][] = "Default value '".$sDefaultValue."' for attribute $sAttCode is not an allowed value"; - $aSugFix[$sClass][] = "Please pickup the default value out of {'".implode(", ", array_keys($aAllowedValues))."'}"; - } - } - } - } - // Check dependencies - if ($oAttDef->IsWritable()) - { - foreach ($oAttDef->GetPrerequisiteAttributes() as $sDependOnAttCode) - { - if (!self::IsValidAttCode($sClass, $sDependOnAttCode)) - { - $aErrors[$sClass][] = "Unkown attribute code '".$sDependOnAttCode."' in the list of prerequisite attributes"; - $aSugFix[$sClass][] = "Expecting a value in ".implode(", ", self::GetAttributesList($sClass)); - } - } - } - } - foreach(self::GetClassFilterDefs($sClass) as $sFltCode=>$oFilterDef) - { - if (method_exists($oFilterDef, '__GetRefAttribute')) - { - $oAttDef = $oFilterDef->__GetRefAttribute(); - if (!self::IsValidAttCode($sClass, $oAttDef->GetCode())) - { - $aErrors[$sClass][] = "Wrong attribute code '".$oAttDef->GetCode()."' (wrong class) for the \"basic\" filter $sFltCode"; - $aSugFix[$sClass][] = "Expecting a value in {".implode(", ", self::GetAttributesList($sClass))."}"; - } - } - } - - // Lifecycle - // - $sStateAttCode = self::GetStateAttributeCode($sClass); - if (strlen($sStateAttCode) > 0) - { - // Lifecycle - check that the state attribute does exist as an attribute - if (!self::IsValidAttCode($sClass, $sStateAttCode)) - { - $aErrors[$sClass][] = "Unkown attribute code '".$sStateAttCode."' for the state definition"; - $aSugFix[$sClass][] = "Expecting a value in {".implode(", ", self::GetAttributesList($sClass))."}"; - } - else - { - // Lifecycle - check that there is a value set constraint on the state attribute - $aAllowedValuesRaw = self::GetAllowedValues_att($sClass, $sStateAttCode); - $aStates = array_keys(self::EnumStates($sClass)); - if (is_null($aAllowedValuesRaw)) - { - $aErrors[$sClass][] = "Attribute '".$sStateAttCode."' will reflect the state of the object. It must be restricted to a set of values"; - $aSugFix[$sClass][] = "Please define its allowed_values property as [new ValueSetEnum('".implode(", ", $aStates)."')]"; - } - else - { - $aAllowedValues = array_keys($aAllowedValuesRaw); - - // Lifecycle - check the the state attribute allowed values are defined states - foreach($aAllowedValues as $sValue) - { - if (!in_array($sValue, $aStates)) - { - $aErrors[$sClass][] = "Attribute '".$sStateAttCode."' (object state) has an allowed value ($sValue) which is not a known state"; - $aSugFix[$sClass][] = "You may define its allowed_values property as [new ValueSetEnum('".implode(", ", $aStates)."')], or reconsider the list of states"; - } - } - - // Lifecycle - check that defined states are allowed values - foreach($aStates as $sStateValue) - { - if (!in_array($sStateValue, $aAllowedValues)) - { - $aErrors[$sClass][] = "Attribute '".$sStateAttCode."' (object state) has a state ($sStateValue) which is not an allowed value"; - $aSugFix[$sClass][] = "You may define its allowed_values property as [new ValueSetEnum('".implode(", ", $aStates)."')], or reconsider the list of states"; - } - } - } - - // Lifcycle - check that the action handlers are defined - foreach (self::EnumStates($sClass) as $sStateCode => $aStateDef) - { - foreach(self::EnumTransitions($sClass, $sStateCode) as $sStimulusCode => $aTransitionDef) - { - foreach ($aTransitionDef['actions'] as $sActionHandler) - { - if (!method_exists($sClass, $sActionHandler)) - { - $aErrors[$sClass][] = "Unknown function '$sActionHandler' in transition [$sStateCode/$sStimulusCode] for state attribute '$sStateAttCode'"; - $aSugFix[$sClass][] = "Specify a function which prototype is in the form [public function $sActionHandler(\$sStimulusCode){return true;}]"; - } - } - } - } - } - } - - // ZList - // - foreach(self::EnumZLists() as $sListCode) - { - foreach (self::GetZListItems($sClass, $sListCode) as $sMyAttCode) - { - if (!self::IsValidAttCode($sClass, $sMyAttCode)) - { - $aErrors[$sClass][] = "Unkown attribute code '".$sMyAttCode."' from ZList '$sListCode'"; - $aSugFix[$sClass][] = "Expecting a value in {".implode(", ", self::GetAttributesList($sClass))."}"; - } - } - } - } - if (count($aErrors) > 0) - { - echo "
"; - echo "

Business model inconsistencies have been found

\n"; - // #@# later -> this is the responsibility of the caller to format the output - foreach ($aErrors as $sClass => $aMessages) - { - echo "

Wrong declaration for class $sClass

\n"; - echo "\n"; - } - echo "

Aborting...

\n"; - echo "
\n"; - exit; - } - } - - public static function DBShowApplyForm($sRepairUrl, $sSQLStatementArgName, $aSQLFixes) - { - if (empty($sRepairUrl)) return; - if (count($aSQLFixes) == 0) return; - - echo "
\n"; - echo " \n"; - echo " \n"; - echo "
\n"; - } - - public static function DBExists($bMustBeComplete = true) - { - // returns true if at least one table exists - // - - if (!CMDBSource::IsDB(self::$m_sDBName)) - { - return false; - } - CMDBSource::SelectDB(self::$m_sDBName); - - $aFound = array(); - $aMissing = array(); - foreach (self::DBEnumTables() as $sTable => $aClasses) - { - if (CMDBSource::IsTable($sTable)) - { - $aFound[] = $sTable; - } - else - { - $aMissing[] = $sTable; - } - } - - if (count($aFound) == 0) - { - // no expected table has been found - return false; - } - else - { - if (count($aMissing) == 0) - { - // the database is complete (still, could be some fields missing!) - return true; - } - else - { - // not all the tables, could be an older version - if ($bMustBeComplete) - { - return false; - } - else - { - return true; - } - } - } - } - - public static function DBDrop() - { - $bDropEntireDB = true; - - if (!empty(self::$m_sTablePrefix)) - { - // Do drop only tables corresponding to the sub-database (table prefix) - // then possibly drop the DB itself (if no table remain) - foreach (CMDBSource::EnumTables() as $sTable) - { - // perform a case insensitive test because on Windows the table names become lowercase :-( - if (strtolower(substr($sTable, 0, strlen(self::$m_sTablePrefix))) == strtolower(self::$m_sTablePrefix)) - { - CMDBSource::DropTable($sTable); - } - else - { - // There is at least one table which is out of the scope of the current application - $bDropEntireDB = false; - } - } - } - - if ($bDropEntireDB) - { - CMDBSource::DropDB(self::$m_sDBName); - } - } - - - public static function DBCreate() - { - // Note: we have to check if the DB does exist, because we may share the DB - // with other applications (in which case the DB does exist, not the tables with the given prefix) - if (!CMDBSource::IsDB(self::$m_sDBName)) - { - CMDBSource::CreateDB(self::$m_sDBName); - } - self::DBCreateTables(); - } - - protected static function DBCreateTables() - { - list($aErrors, $aSugFix) = self::DBCheckFormat(); - - $aSQL = array(); - foreach ($aSugFix as $sClass => $aTarget) - { - foreach ($aTarget as $aQueries) - { - foreach ($aQueries as $sQuery) - { - //$aSQL[] = $sQuery; - // forces a refresh of cached information - CMDBSource::CreateTable($sQuery); - } - } - } - // does not work -how to have multiple statements in a single query? - // $sDoCreateAll = implode(" ; ", $aSQL); - } - - public static function DBDump() - { - $aDataDump = array(); - foreach (self::DBEnumTables() as $sTable => $aClasses) - { - $aRows = CMDBSource::DumpTable($sTable); - $aDataDump[$sTable] = $aRows; - } - return $aDataDump; - } - - public static function DBCheckFormat() - { - $aErrors = array(); - $aSugFix = array(); - foreach (self::GetClasses() as $sClass) - { - if (self::IsAbstract($sClass)) continue; - - // Check that the table exists - // - $sTable = self::DBGetTable($sClass); - $sKeyField = self::DBGetKey($sClass); - $sAutoIncrement = (self::IsAutoIncrementKey($sClass) ? "AUTO_INCREMENT" : ""); - if (!CMDBSource::IsTable($sTable)) - { - $aErrors[$sClass]['*'][] = "table '$sTable' could not be found into the DB"; - $aSugFix[$sClass]['*'][] = "CREATE TABLE `$sTable` (`$sKeyField` INT(11) NOT NULL $sAutoIncrement PRIMARY KEY) ENGINE = innodb CHARACTER SET utf8 COLLATE utf8_unicode_ci"; - } - // Check that the key field exists - // - elseif (!CMDBSource::IsField($sTable, $sKeyField)) - { - $aErrors[$sClass]['id'][] = "key '$sKeyField' (table $sTable) could not be found"; - $aSugFix[$sClass]['id'][] = "ALTER TABLE `$sTable` ADD `$sKeyField` INT(11) NOT NULL $sAutoIncrement PRIMARY KEY"; - } - else - { - // Check the key field properties - // - if (!CMDBSource::IsKey($sTable, $sKeyField)) - { - $aErrors[$sClass]['id'][] = "key '$sKeyField' is not a key for table '$sTable'"; - $aSugFix[$sClass]['id'][] = "ALTER TABLE `$sTable`, DROP PRIMARY KEY, ADD PRIMARY key(`$sKeyField`)"; - } - if (self::IsAutoIncrementKey($sClass) && !CMDBSource::IsAutoIncrement($sTable, $sKeyField)) - { - $aErrors[$sClass]['id'][] = "key '$sKeyField' (table $sTable) is not automatically incremented"; - $aSugFix[$sClass]['id'][] = "ALTER TABLE `$sTable` CHANGE `$sKeyField` `$sKeyField` INT(11) NOT NULL AUTO_INCREMENT"; - } - } - - // Check that any defined field exists - // - $aTableInfo = CMDBSource::GetTableInfo($sTable); - - foreach(self::ListAttributeDefs($sClass) as $sAttCode=>$oAttDef) - { - // Skip this attribute if not originaly defined in this class - if (self::$m_aAttribOrigins[$sClass][$sAttCode] != $sClass) continue; - - foreach($oAttDef->GetSQLColumns() as $sField => $sDBFieldType) - { - $sFieldSpecs = $oAttDef->IsNullAllowed() ? "$sDBFieldType NULL" : "$sDBFieldType NOT NULL"; - if (!CMDBSource::IsField($sTable, $sField)) - { - $aErrors[$sClass][$sAttCode][] = "field '$sField' could not be found in table '$sTable'"; - $aSugFix[$sClass][$sAttCode][] = "ALTER TABLE `$sTable` ADD `$sField` $sFieldSpecs"; - if ($oAttDef->IsExternalKey()) - { - $aSugFix[$sClass][$sAttCode][] = "ALTER TABLE `$sTable` ADD INDEX (`$sField`)"; - } - } - else - { - // The field already exists, does it have the relevant properties? - // - $bToBeChanged = false; - if ($oAttDef->IsNullAllowed() != CMDBSource::IsNullAllowed($sTable, $sField)) - { - $bToBeChanged = true; - if ($oAttDef->IsNullAllowed()) - { - $aErrors[$sClass][$sAttCode][] = "field '$sField' in table '$sTable' could be NULL"; - } - else - { - $aErrors[$sClass][$sAttCode][] = "field '$sField' in table '$sTable' could NOT be NULL"; - } - } - $sActualFieldType = CMDBSource::GetFieldType($sTable, $sField); - if (strcasecmp($sDBFieldType, $sActualFieldType) != 0) - { - $bToBeChanged = true; - $aErrors[$sClass][$sAttCode][] = "field '$sField' in table '$sTable' has a wrong type: found '$sActualFieldType' while expecting '$sDBFieldType'"; - } - if ($bToBeChanged) - { - $aSugFix[$sClass][$sAttCode][] = "ALTER TABLE `$sTable` CHANGE `$sField` `$sField` $sFieldSpecs"; - } - - // Create indexes (external keys only... so far) - // - if ($oAttDef->IsExternalKey() && !CMDBSource::HasIndex($sTable, $sField)) - { - $aErrors[$sClass][$sAttCode][] = "Foreign key '$sField' in table '$sTable' should have an index"; - $aSugFix[$sClass][$sAttCode][] = "ALTER TABLE `$sTable` ADD INDEX (`$sField`)"; - } - } - } - } - } - return array($aErrors, $aSugFix); - } - - - private static function DBCheckIntegrity_Check2Delete($sSelWrongRecs, $sErrorDesc, $sClass, &$aErrorsAndFixes, &$iNewDelCount, &$aPlannedDel, $bProcessingFriends = false) - { - $sRootClass = self::GetRootClass($sClass); - $sTable = self::DBGetTable($sClass); - $sKeyField = self::DBGetKey($sClass); - - if (array_key_exists($sTable, $aPlannedDel) && count($aPlannedDel[$sTable]) > 0) - { - $sSelWrongRecs .= " AND maintable.`$sKeyField` NOT IN ('".implode("', '", $aPlannedDel[$sTable])."')"; - } - $aWrongRecords = CMDBSource::QueryToCol($sSelWrongRecs, "id"); - if (count($aWrongRecords) == 0) return; - - if (!array_key_exists($sRootClass, $aErrorsAndFixes)) $aErrorsAndFixes[$sRootClass] = array(); - if (!array_key_exists($sTable, $aErrorsAndFixes[$sRootClass])) $aErrorsAndFixes[$sRootClass][$sTable] = array(); - - foreach ($aWrongRecords as $iRecordId) - { - if (array_key_exists($iRecordId, $aErrorsAndFixes[$sRootClass][$sTable])) - { - switch ($aErrorsAndFixes[$sRootClass][$sTable][$iRecordId]['Action']) - { - case 'Delete': - // Already planned for a deletion - // Let's concatenate the errors description together - $aErrorsAndFixes[$sRootClass][$sTable][$iRecordId]['Reason'] .= ', '.$sErrorDesc; - break; - - case 'Update': - // Let's plan a deletion - break; - } - } - else - { - $aErrorsAndFixes[$sRootClass][$sTable][$iRecordId]['Reason'] = $sErrorDesc; - } - - if (!$bProcessingFriends) - { - if (!array_key_exists($sTable, $aPlannedDel) || !in_array($iRecordId, $aPlannedDel[$sTable])) - { - // Something new to be deleted... - $iNewDelCount++; - } - } - - $aErrorsAndFixes[$sRootClass][$sTable][$iRecordId]['Action'] = 'Delete'; - $aErrorsAndFixes[$sRootClass][$sTable][$iRecordId]['Action_Details'] = array(); - $aErrorsAndFixes[$sRootClass][$sTable][$iRecordId]['Pass'] = 123; - $aPlannedDel[$sTable][] = $iRecordId; - } - - // Now make sure that we would delete the records of the other tables for that class - // - if (!$bProcessingFriends) - { - $sDeleteKeys = "'".implode("', '", $aWrongRecords)."'"; - foreach (self::EnumChildClasses($sRootClass, ENUM_CHILD_CLASSES_ALL) as $sFriendClass) - { - $sFriendTable = self::DBGetTable($sFriendClass); - $sFriendKey = self::DBGetKey($sFriendClass); - - // skip the current table - if ($sFriendTable == $sTable) continue; - - $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 `$sTable`", $sFriendClass, $aErrorsAndFixes, $iNewDelCount, $aPlannedDel, true); - } - } - } - - private static function DBCheckIntegrity_Check2Update($sSelWrongRecs, $sErrorDesc, $sColumn, $sNewValue, $sClass, &$aErrorsAndFixes, &$iNewDelCount, &$aPlannedDel) - { - $sRootClass = self::GetRootClass($sClass); - $sTable = self::DBGetTable($sClass); - $sKeyField = self::DBGetKey($sClass); - - if (array_key_exists($sTable, $aPlannedDel) && count($aPlannedDel[$sTable]) > 0) - { - $sSelWrongRecs .= " AND maintable.`$sKeyField` NOT IN ('".implode("', '", $aPlannedDel[$sTable])."')"; - } - $aWrongRecords = CMDBSource::QueryToCol($sSelWrongRecs, "id"); - if (count($aWrongRecords) == 0) return; - - if (!array_key_exists($sRootClass, $aErrorsAndFixes)) $aErrorsAndFixes[$sRootClass] = array(); - if (!array_key_exists($sTable, $aErrorsAndFixes[$sRootClass])) $aErrorsAndFixes[$sRootClass][$sTable] = array(); - - foreach ($aWrongRecords as $iRecordId) - { - if (array_key_exists($iRecordId, $aErrorsAndFixes[$sRootClass][$sTable])) - { - switch ($aErrorsAndFixes[$sRootClass][$sTable][$iRecordId]['Action']) - { - case 'Delete': - // No need to update, the record will be deleted! - break; - - case 'Update': - // Already planned for an update - // Add this new update spec to the list - $bFoundSameSpec = false; - foreach ($aErrorsAndFixes[$sRootClass][$sTable][$iRecordId]['Action_Details'] as $aUpdateSpec) - { - if (($sColumn == $aUpdateSpec['column']) && ($sNewValue == $aUpdateSpec['newvalue'])) - { - $bFoundSameSpec = true; - } - } - if (!$bFoundSameSpec) - { - $aErrorsAndFixes[$sRootClass][$sTable][$iRecordId]['Action_Details'][] = (array('column' => $sColumn, 'newvalue'=>$sNewValue)); - $aErrorsAndFixes[$sRootClass][$sTable][$iRecordId]['Reason'] .= ', '.$sErrorDesc; - } - break; - } - } - else - { - $aErrorsAndFixes[$sRootClass][$sTable][$iRecordId]['Reason'] = $sErrorDesc; - $aErrorsAndFixes[$sRootClass][$sTable][$iRecordId]['Action'] = 'Update'; - $aErrorsAndFixes[$sRootClass][$sTable][$iRecordId]['Action_Details'] = array(array('column' => $sColumn, 'newvalue'=>$sNewValue)); - $aErrorsAndFixes[$sRootClass][$sTable][$iRecordId]['Pass'] = 123; - } - - } - } - - // returns the count of records found for deletion - public static function DBCheckIntegrity_SinglePass(&$aErrorsAndFixes, &$iNewDelCount, &$aPlannedDel) - { - foreach (self::GetClasses() as $sClass) - { - if (self::IsAbstract($sClass)) continue; - $sRootClass = self::GetRootClass($sClass); - $sTable = self::DBGetTable($sClass); - $sKeyField = self::DBGetKey($sClass); - - // Check that the final class field contains the name of a class which inherited from the current class - // - if (self::HasFinalClassField($sClass)) - { - $sFinalClassField = self::DBGetClassField($sClass); - - $aAllowedValues = self::EnumChildClasses($sClass, ENUM_CHILD_CLASSES_ALL); - $sAllowedValues = implode(",", CMDBSource::Quote($aAllowedValues, true)); - - $sSelWrongRecs = "SELECT DISTINCT maintable.`$sKeyField` AS id FROM `$sTable` AS maintable WHERE `$sFinalClassField` NOT IN ($sAllowedValues)"; - self::DBCheckIntegrity_Check2Delete($sSelWrongRecs, "final class (field `$sFinalClassField`) is wrong (expected a value in {".$sAllowedValues."})", $sClass, $aErrorsAndFixes, $iNewDelCount, $aPlannedDel); - } - - // Compound objects model - node/leaf classes (not the root itself) - // - if (!self::IsStandaloneClass($sClass) && !self::HasFinalClassField($sClass)) - { - $sRootTable = self::DBGetTable($sRootClass); - $sRootKey = self::DBGetKey($sRootClass); - $sFinalClassField = self::DBGetClassField($sRootClass); - - $aExpectedClasses = self::EnumChildClasses($sClass, ENUM_CHILD_CLASSES_ALL); - $sExpectedClasses = implode(",", CMDBSource::Quote($aExpectedClasses, true)); - - // 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 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 `$sTable`, but no counterpart in root table `$sRootTable` (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 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 `$sRootTable`, but no counterpart in table `$sTable` (root records pointing to a class in {".$sExpectedClasses."})", $sRootClass, $aErrorsAndFixes, $iNewDelCount, $aPlannedDel); - } - - foreach(self::ListAttributeDefs($sClass) as $sAttCode=>$oAttDef) - { - // Skip this attribute if not defined in this table - if (self::$m_aAttribOrigins[$sClass][$sAttCode] != $sClass) continue; - - if ($oAttDef->IsExternalKey()) - { - // Check that any external field is pointing to an existing object - // - $sRemoteClass = $oAttDef->GetTargetClass(); - $sRemoteTable = self::DBGetTable($sRemoteClass); - $sRemoteKey = self::DBGetKey($sRemoteClass); - - $sExtKeyField = current($oAttDef->GetSQLExpressions()); // get the first column for an external key - - // Note: a class/table may have an external key on itself - $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()) - { - // Exclude the records pointing to 0/null from the errors - $sSelWrongRecs .= " AND maintable.`$sExtKeyField` IS NOT NULL"; - $sSelWrongRecs .= " AND maintable.`$sExtKeyField` != 0"; - self::DBCheckIntegrity_Check2Update($sSelWrongRecs, "Record pointing to (external key '$sAttCode') non existing objects", $sExtKeyField, 'null', $sClass, $aErrorsAndFixes, $iNewDelCount, $aPlannedDel); - } - else - { - self::DBCheckIntegrity_Check2Delete($sSelWrongRecs, "Record pointing to (external key '$sAttCode') non existing objects", $sClass, $aErrorsAndFixes, $iNewDelCount, $aPlannedDel); - } - - // Do almost the same, taking into account the records planned for deletion - if (array_key_exists($sRemoteTable, $aPlannedDel) && count($aPlannedDel[$sRemoteTable]) > 0) - { - // This could be done by the mean of a 'OR ... IN (aIgnoreRecords) - // but in that case you won't be able to track the root cause (cascading) - $sSelWrongRecs = $sSelBase." WHERE maintable.`$sExtKeyField` IN ('".implode("', '", $aPlannedDel[$sRemoteTable])."')"; - if ($oAttDef->IsNullAllowed()) - { - // Exclude the records pointing to 0/null from the errors - $sSelWrongRecs .= " AND maintable.`$sExtKeyField` IS NOT NULL"; - $sSelWrongRecs .= " AND maintable.`$sExtKeyField` != 0"; - self::DBCheckIntegrity_Check2Update($sSelWrongRecs, "Record pointing to (external key '$sAttCode') a record planned for deletion", $sExtKeyField, 'null', $sClass, $aErrorsAndFixes, $iNewDelCount, $aPlannedDel); - } - else - { - self::DBCheckIntegrity_Check2Delete($sSelWrongRecs, "Record pointing to (external key '$sAttCode') a record planned for deletion", $sClass, $aErrorsAndFixes, $iNewDelCount, $aPlannedDel); - } - } - } - else if ($oAttDef->IsDirectField()) - { - // Check that the values fit the allowed values - // - $aAllowedValues = self::GetAllowedValues_att($sClass, $sAttCode); - if (!is_null($aAllowedValues) && count($aAllowedValues) > 0) - { - $sExpectedValues = implode(",", CMDBSource::Quote(array_keys($aAllowedValues), true)); - - $sMyAttributeField = current($oAttDef->GetSQLExpressions()); // get the first column for the moment - $sDefaultValue = $oAttDef->GetDefaultValue(); - $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 ('$sAttCode') with an unexpected value", $sMyAttributeField, CMDBSource::Quote($sDefaultValue), $sClass, $aErrorsAndFixes, $iNewDelCount, $aPlannedDel); - } - } - } - } - } - - public static function DBCheckIntegrity($sRepairUrl = "", $sSQLStatementArgName = "") - { - // Records in error, and action to be taken: delete or update - // by RootClass/Table/Record - $aErrorsAndFixes = array(); - - // Records to be ignored in the current/next pass - // by Table = array of RecordId - $aPlannedDel = array(); - - // Count of errors in the next pass: no error means that we can leave... - $iErrorCount = 0; - // Limit in case of a bug in the algorythm - $iLoopCount = 0; - - $iNewDelCount = 1; // startup... - while ($iNewDelCount > 0) - { - $iNewDelCount = 0; - self::DBCheckIntegrity_SinglePass($aErrorsAndFixes, $iNewDelCount, $aPlannedDel); - $iErrorCount += $iNewDelCount; - - // Safety net #1 - limit the planned deletions - // - $iMaxDel = 1000; - $iPlannedDel = 0; - foreach ($aPlannedDel as $sTable => $aPlannedDelOnTable) - { - $iPlannedDel += count($aPlannedDelOnTable); - } - if ($iPlannedDel > $iMaxDel) - { - throw new CoreWarning("DB Integrity Check safety net - Exceeding the limit of $iMaxDel planned record deletion"); - break; - } - // Safety net #2 - limit the iterations - // - $iLoopCount++; - $iMaxLoops = 10; - if ($iLoopCount > $iMaxLoops) - { - throw new CoreWarning("DB Integrity Check safety net - Reached the limit of $iMaxLoops loops"); - break; - } - } - - // Display the results - // - $iIssueCount = 0; - $aFixesDelete = array(); - $aFixesUpdate = array(); - - foreach ($aErrorsAndFixes as $sRootClass => $aTables) - { - foreach ($aTables as $sTable => $aRecords) - { - foreach ($aRecords as $iRecord => $aError) - { - $sAction = $aError['Action']; - $sReason = $aError['Reason']; - $iPass = $aError['Pass']; - - switch ($sAction) - { - case 'Delete': - $sActionDetails = ""; - $aFixesDelete[$sTable][] = $iRecord; - break; - - case 'Update': - $aUpdateDesc = array(); - foreach($aError['Action_Details'] as $aUpdateSpec) - { - $aUpdateDesc[] = $aUpdateSpec['column']." -> ".$aUpdateSpec['newvalue']; - $aFixesUpdate[$sTable][$aUpdateSpec['column']][$aUpdateSpec['newvalue']][] = $iRecord; - } - $sActionDetails = "Set ".implode(", ", $aUpdateDesc); - - break; - - default: - $sActionDetails = "bug: unknown action '$sAction'"; - } - $aIssues[] = "$sRootClass / $sTable / $iRecord / $sReason / $sAction / $sActionDetails"; - $iIssueCount++; - } - } - } - - if ($iIssueCount > 0) - { - // Build the queries to fix in the database - // - // First step, be able to get class data out of the table name - // Could be optimized, because we've made the job earlier... but few benefits, so... - $aTable2ClassProp = array(); - foreach (self::GetClasses() as $sClass) - { - if (self::IsAbstract($sClass)) continue; - - $sRootClass = self::GetRootClass($sClass); - $sTable = self::DBGetTable($sClass); - $sKeyField = self::DBGetKey($sClass); - - $aErrorsAndFixes[$sRootClass][$sTable] = array(); - $aTable2ClassProp[$sTable] = array('rootclass'=>$sRootClass, 'class'=>$sClass, 'keyfield'=>$sKeyField); - } - // Second step, build a flat list of SQL queries - $aSQLFixes = array(); - $iPlannedUpdate = 0; - foreach ($aFixesUpdate as $sTable => $aColumns) - { - foreach ($aColumns as $sColumn => $aNewValues) - { - foreach ($aNewValues as $sNewValue => $aRecords) - { - $iPlannedUpdate += count($aRecords); - $sWrongRecords = "'".implode("', '", $aRecords)."'"; - $sKeyField = $aTable2ClassProp[$sTable]['keyfield']; - - $aSQLFixes[] = "UPDATE `$sTable` SET `$sColumn` = $sNewValue WHERE `$sKeyField` IN ($sWrongRecords)"; - } - } - } - $iPlannedDel = 0; - foreach ($aFixesDelete as $sTable => $aRecords) - { - $iPlannedDel += count($aRecords); - $sWrongRecords = "'".implode("', '", $aRecords)."'"; - $sKeyField = $aTable2ClassProp[$sTable]['keyfield']; - - $aSQLFixes[] = "DELETE FROM `$sTable` WHERE `$sKeyField` IN ($sWrongRecords)"; - } - - // Report the results - // - echo "
"; - echo "

Database corruption error(s): $iErrorCount issues have been encountered. $iPlannedDel records will be deleted, $iPlannedUpdate records will be updated:

\n"; - // #@# later -> this is the responsibility of the caller to format the output - echo "\n"; - self::DBShowApplyForm($sRepairUrl, $sSQLStatementArgName, $aSQLFixes); - echo "

Aborting...

\n"; - echo "
\n"; - exit; - } - } - - public static function Startup($sConfigFile, $bAllowMissingDB = false) - { - self::LoadConfig($sConfigFile); - if (self::DBExists()) - { - CMDBSource::SelectDB(self::$m_sDBName); - - // Some of the init could not be done earlier (requiring classes to be declared and DB to be accessible) - self::InitPlugins(); - } - else - { - if (!$bAllowMissingDB) - { - throw new CoreException('Database not found, check your configuration file', array('config_file'=>$sConfigFile, 'db_name'=>self::$m_sDBName)); - } - } - } - - public static function LoadConfig($sConfigFile) - { - $oConfig = new Config($sConfigFile); - - foreach ($oConfig->GetAppModules() as $sModule => $sToInclude) - { - self::Plugin($sConfigFile, 'application', $sToInclude); - } - foreach ($oConfig->GetDataModels() as $sModule => $sToInclude) - { - self::Plugin($sConfigFile, 'business', $sToInclude); - } - foreach ($oConfig->GetAddons() as $sModule => $sToInclude) - { - self::Plugin($sConfigFile, 'addons', $sToInclude); - } - - $sServer = $oConfig->GetDBHost(); - $sUser = $oConfig->GetDBUser(); - $sPwd = $oConfig->GetDBPwd(); - $sSource = $oConfig->GetDBName(); - $sTablePrefix = $oConfig->GetDBSubname(); - - // The include have been included, let's browse the existing classes and - // develop some data based on the proposed model - self::InitClasses($sTablePrefix); - - self::$m_sDBName = $sSource; - self::$m_sTablePrefix = $sTablePrefix; - - CMDBSource::Init($sServer, $sUser, $sPwd); // do not select the DB (could not exist) - } - - protected static $m_aPlugins = array(); - public static function RegisterPlugin($sType, $sName, $aInitCallSpec = array()) - { - self::$m_aPlugins[$sName] = array( - 'type' => $sType, - 'init' => $aInitCallSpec, - ); - } - - protected static function Plugin($sConfigFile, $sModuleType, $sToInclude) - { - if (!file_exists($sToInclude)) - { - throw new CoreException('Wrong filename in configuration file', array('file' => $sConfigFile, 'module' => $sModuleType, 'filename' => $sToInclude)); - } - require_once($sToInclude); - } - - protected static function InitPlugins() - { - foreach(self::$m_aPlugins as $sName => $aData) - { - $aCallSpec = @$aData['init']; - if (count($aCallSpec) == 2) - { - if (!is_callable($aCallSpec)) - { - throw new CoreException('Wrong declaration in plugin', array('plugin' => $aData['name'], 'type' => $aData['type'], 'class' => $aData['class'], 'init' => $aData['init'])); - } - call_user_func($aCallSpec); - } - } - } - - // Building an object - // - // - private static $aQueryCacheGetObject = array(); - private static $aQueryCacheGetObjectHits = array(); - public static function GetQueryCacheStatus() - { - $aRes = array(); - $iTotalHits = 0; - foreach(self::$aQueryCacheGetObjectHits as $sClass => $iHits) - { - $aRes[] = "$sClass: $iHits"; - $iTotalHits += $iHits; - } - return $iTotalHits.' ('.implode(', ', $aRes).')'; - } - - public static function MakeSingleRow($sClass, $iKey, $bMustBeFound = true) - { - if (!array_key_exists($sClass, self::$aQueryCacheGetObject)) - { - // NOTE: Quick and VERY dirty caching mechanism which relies on - // the fact that the string '987654321' will never appear in the - // standard query - // This will be replaced for sure with a prepared statement - // or a view... next optimization to come! - $oFilter = new DBObjectSearch($sClass); - $oFilter->AddCondition('id', 987654321, '='); - - $sSQL = self::MakeSelectQuery($oFilter); - self::$aQueryCacheGetObject[$sClass] = $sSQL; - self::$aQueryCacheGetObjectHits[$sClass] = 0; - } - else - { - $sSQL = self::$aQueryCacheGetObject[$sClass]; - self::$aQueryCacheGetObjectHits[$sClass] += 1; -// echo " -load $sClass/$iKey- ".self::$aQueryCacheGetObjectHits[$sClass]."
\n"; - } - $sSQL = str_replace('987654321', CMDBSource::Quote($iKey), $sSQL); - $res = CMDBSource::Query($sSQL); - - $aRow = CMDBSource::FetchArray($res); - CMDBSource::FreeResult($res); - if ($bMustBeFound && empty($aRow)) - { - throw new CoreException("No result for the single row query: '$sSQL'"); - } - return $aRow; - } - - public static function GetObjectByRow($sClass, $aRow) - { - self::_check_subclass($sClass); - - // Compound objects: if available, get the final object class - // - if (!array_key_exists("finalclass", $aRow)) - { - // Either this is a bug (forgot to specify a root class with a finalclass field - // Or this is the expected behavior, because the object is not made of several tables - } - elseif (empty($aRow["finalclass"])) - { - // The data is missing in the DB - // @#@ possible improvement: check that the class is valid ! - $sRootClass = self::GetRootClass($sClass); - $sFinalClassField = self::DBGetClassField($sRootClass); - throw new CoreException("Empty class name for object $sClass::{$aRow["id"]} (root class '$sRootClass', field '{$sFinalClassField}' is empty)"); - } - else - { - // do the job for the real target class - $sClass = $aRow["finalclass"]; - } - return new $sClass($aRow); - } - - public static function GetObject($sClass, $iKey, $bMustBeFound = true) - { - self::_check_subclass($sClass); - $aRow = self::MakeSingleRow($sClass, $iKey, $bMustBeFound); - if (empty($aRow)) - { - return null; - } - return self::GetObjectByRow($sClass, $aRow); - } - - public static function GetHyperLink($sTargetClass, $iKey) - { - if ($iKey < 0) - { - return "$sTargetClass: $iKey (invalid value)"; - } - $oObj = self::GetObject($sTargetClass, $iKey, false); - if (is_null($oObj)) - { - return "$sTargetClass: $iKey (not found)"; - } - return $oObj->GetHyperLink(); - } - - public static function NewObject($sClass) - { - self::_check_subclass($sClass); - return new $sClass(); - } - - public static function GetNextKey($sClass) - { - $sRootClass = MetaModel::GetRootClass($sClass); - $sRootTable = MetaModel::DBGetTable($sRootClass); - $iNextKey = CMDBSource::GetNextInsertId($sRootTable); - return $iNextKey; - } - - public static function BulkDelete(DBObjectSearch $oFilter) - { - $sSQL = self::MakeDeleteQuery($oFilter); - CMDBSource::Query($sSQL); - } - - public static function BulkUpdate(DBObjectSearch $oFilter, array $aValues) - { - // $aValues is an array of $sAttCode => $value - $sSQL = self::MakeUpdateQuery($oFilter, $aValues); - CMDBSource::Query($sSQL); - } - - // Links - // - // - public static function EnumReferencedClasses($sClass) - { - self::_check_subclass($sClass); - - // 1-N links (referenced by my class), returns an array of sAttCode=>sClass - $aResult = array(); - foreach(self::$m_aAttribDefs[$sClass] as $sAttCode=>$oAttDef) - { - if ($oAttDef->IsExternalKey()) - { - $aResult[$sAttCode] = $oAttDef->GetTargetClass(); - } - } - return $aResult; - } - public static function EnumReferencingClasses($sClass, $bSkipLinkingClasses = false, $bInnerJoinsOnly = false) - { - self::_check_subclass($sClass); - - if ($bSkipLinkingClasses) - { - $aLinksClasses = self::EnumLinksClasses(); - } - - // 1-N links (referencing my class), array of sClass => array of sAttcode - $aResult = array(); - foreach (self::$m_aAttribDefs as $sSomeClass=>$aClassAttributes) - { - if ($bSkipLinkingClasses && in_array($sSomeClass, $aLinksClasses)) continue; - - $aExtKeys = array(); - foreach ($aClassAttributes as $sAttCode=>$oAttDef) - { - if (self::$m_aAttribOrigins[$sSomeClass][$sAttCode] != $sSomeClass) continue; - if ($oAttDef->IsExternalKey() && (self::IsParentClass($oAttDef->GetTargetClass(), $sClass))) - { - if ($bInnerJoinsOnly && $oAttDef->IsNullAllowed()) continue; - // Ok, I want this one - $aExtKeys[$sAttCode] = $oAttDef; - } - } - if (count($aExtKeys) != 0) - { - $aResult[$sSomeClass] = $aExtKeys; - } - } - return $aResult; - } - public static function EnumLinksClasses() - { - // Returns a flat array of classes having at least two external keys - $aResult = array(); - foreach (self::$m_aAttribDefs as $sSomeClass=>$aClassAttributes) - { - $iExtKeyCount = 0; - foreach ($aClassAttributes as $sAttCode=>$oAttDef) - { - if (self::$m_aAttribOrigins[$sSomeClass][$sAttCode] != $sSomeClass) continue; - if ($oAttDef->IsExternalKey()) - { - $iExtKeyCount++; - } - } - if ($iExtKeyCount >= 2) - { - $aResult[] = $sSomeClass; - } - } - return $aResult; - } - public static function EnumLinkingClasses($sClass = "") - { - // N-N links, array of sLinkClass => (array of sAttCode=>sClass) - $aResult = array(); - foreach (self::EnumLinksClasses() as $sSomeClass) - { - $aTargets = array(); - $bFoundClass = false; - foreach (self::ListAttributeDefs($sSomeClass) as $sAttCode=>$oAttDef) - { - if (self::$m_aAttribOrigins[$sSomeClass][$sAttCode] != $sSomeClass) continue; - if ($oAttDef->IsExternalKey()) - { - $sRemoteClass = $oAttDef->GetTargetClass(); - if (empty($sClass)) - { - $aTargets[$sAttCode] = $sRemoteClass; - } - elseif ($sClass == $sRemoteClass) - { - $bFoundClass = true; - } - else - { - $aTargets[$sAttCode] = $sRemoteClass; - } - } - } - if (empty($sClass) || $bFoundClass) - { - $aResult[$sSomeClass] = $aTargets; - } - } - return $aResult; - } - - public static function GetLinkLabel($sLinkClass, $sAttCode) - { - self::_check_subclass($sLinkClass); - - // e.g. "supported by" (later: $this->GetLinkLabel(), computed on link data!) - return self::GetLabel($sLinkClass, $sAttCode); - } - - /** - * Replaces all the parameters by the values passed in the hash array - */ - static public function ApplyParams($aInput, $aParams) - { - $aSearches = array(); - $aReplacements = array(); - foreach($aParams as $sSearch => $sReplace) - { - $aSearches[] = '$'.$sSearch.'$'; - $aReplacements[] = $sReplace; - } - return str_replace($aSearches, $aReplacements, $aInput); - } - -} // class MetaModel - - -// Standard attribute lists -MetaModel::RegisterZList("noneditable", array("description"=>"non editable fields", "type"=>"attributes")); - -MetaModel::RegisterZList("details", array("description"=>"All attributes to be displayed for the 'details' of an object", "type"=>"attributes")); -MetaModel::RegisterZList("list", array("description"=>"All attributes to be displayed for a list of objects", "type"=>"attributes")); -MetaModel::RegisterZList("preview", array("description"=>"All attributes visible in preview mode", "type"=>"attributes")); - -MetaModel::RegisterZList("standard_search", array("description"=>"List of criteria for the standard search", "type"=>"filters")); -MetaModel::RegisterZList("advanced_search", array("description"=>"List of criteria for the advanced search", "type"=>"filters")); - - -?> + + * @author Denis Flaven + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.itop.com + * @since 1.0 + * @version 1.1.1.1 $ + */ +abstract class MetaModel +{ + /////////////////////////////////////////////////////////////////////////// + // + // STATIC Members + // + /////////////////////////////////////////////////////////////////////////// + + // Purpose: workaround the following limitation = PHP5 does not allow to know the class (derived from the current one) + // from which a static function is called (__CLASS__ and self are interpreted during parsing) + private static function GetCallersPHPClass($sExpectedFunctionName = null) + { + //var_dump(debug_backtrace()); + $aBacktrace = debug_backtrace(); + // $aBacktrace[0] is where we are + // $aBacktrace[1] is the caller of GetCallersPHPClass + // $aBacktrace[1] is the info we want + if (!empty($sExpectedFunctionName)) + { + assert('$aBacktrace[2]["function"] == $sExpectedFunctionName'); + } + return $aBacktrace[2]["class"]; + } + + // Static init -why and how it works + // + // We found the following limitations: + //- it is not possible to define non scalar constants + //- it is not possible to declare a static variable as '= new myclass()' + // Then we had do propose this model, in which a derived (non abstract) + // class should implement Init(), to call InheritAttributes or AddAttribute. + + private static function _check_subclass($sClass) + { + // See also IsValidClass()... ???? #@# + // class is mandatory + // (it is not possible to guess it when called as myderived::...) + if (!array_key_exists($sClass, self::$m_aClassParams)) + { + throw new CoreException("Unknown class '$sClass', expected a value in {".implode(', ', array_keys(self::$m_aClassParams))."}"); + } + } + + public static function static_var_dump() + { + var_dump(get_class_vars(__CLASS__)); + } + + private static $m_bDebugQuery = false; + private static $m_iStackDepthRef = 0; + + public static function StartDebugQuery() + { + $aBacktrace = debug_backtrace(); + self::$m_iStackDepthRef = count($aBacktrace); + self::$m_bDebugQuery = true; + } + public static function StopDebugQuery() + { + self::$m_bDebugQuery = false; + } + public static function DbgTrace($value) + { + if (!self::$m_bDebugQuery) return; + $aBacktrace = debug_backtrace(); + $iCallStackPos = count($aBacktrace) - self::$m_bDebugQuery; + $sIndent = ""; + for ($i = 0 ; $i < $iCallStackPos ; $i++) + { + $sIndent .= " .-=^=-. "; + } + $aCallers = array(); + foreach($aBacktrace as $aStackInfo) + { + $aCallers[] = $aStackInfo["function"]; + } + $sCallers = "Callstack: ".implode(', ', $aCallers); + $sFunction = "".$aBacktrace[1]["function"].""; + + if (is_string($value)) + { + echo "$sIndent$sFunction: $value
\n"; + } + else if (is_object($value)) + { + echo "$sIndent$sFunction:\n
\n";
+			print_r($value);
+			echo "
\n"; + } + else + { + echo "$sIndent$sFunction: $value
\n"; + } + } + + private static $m_bTraceQueries = true; + private static $m_aQueriesLog = array(); + + + private static $m_sDBName = ""; + private static $m_sTablePrefix = ""; // table prefix for the current application instance (allow several applications on the same DB) + private static $m_Category2Class = array(); + private static $m_aRootClasses = array(); // array of "classname" => "rootclass" + private static $m_aParentClasses = array(); // array of ("classname" => array of "parentclass") + private static $m_aChildClasses = array(); // array of ("classname" => array of "childclass") + + private static $m_aClassParams = array(); // array of ("classname" => array of class information) + + static public function GetParentPersistentClass($sRefClass) + { + $sClass = get_parent_class($sRefClass); + if (!$sClass) return ''; + + if ($sClass == 'DBObject') return ''; // Warning: __CLASS__ is lower case in my version of PHP + + // Note: the UI/business model may implement pure PHP classes (intermediate layers) + if (array_key_exists($sClass, self::$m_aClassParams)) + { + return $sClass; + } + return self::GetParentPersistentClass($sClass); + } + + final static public function GetName($sClass) + { + self::_check_subclass($sClass); + return self::$m_aClassParams[$sClass]["name"]; + } + final static public function GetCategory($sClass) + { + self::_check_subclass($sClass); + return self::$m_aClassParams[$sClass]["category"]; + } + final static public function HasCategory($sClass, $sCategory) + { + self::_check_subclass($sClass); + return (strpos(self::$m_aClassParams[$sClass]["category"], $sCategory) !== false); + } + final static public function GetClassDescription($sClass) + { + self::_check_subclass($sClass); + return self::$m_aClassParams[$sClass]["description"]; + } + final static public function IsAutoIncrementKey($sClass) + { + self::_check_subclass($sClass); + return (self::$m_aClassParams[$sClass]["key_type"] == "autoincrement"); + } + final static public function GetKeyLabel($sClass) + { + self::_check_subclass($sClass); + return self::$m_aClassParams[$sClass]["key_label"]; + } + final static public function GetNameAttributeCode($sClass) + { + self::_check_subclass($sClass); + return self::$m_aClassParams[$sClass]["name_attcode"]; + } + final static public function GetStateAttributeCode($sClass) + { + self::_check_subclass($sClass); + return self::$m_aClassParams[$sClass]["state_attcode"]; + } + final static public function GetDefaultState($sClass) + { + $sDefaultState = ''; + $sStateAttrCode = self::GetStateAttributeCode($sClass); + if (!empty($sStateAttrCode)) + { + $oStateAttrDef = self::GetAttributeDef($sClass, $sStateAttrCode); + $sDefaultState = $oStateAttrDef->GetDefaultValue(); + } + return $sDefaultState; + } + final static public function GetReconcKeys($sClass) + { + self::_check_subclass($sClass); + return self::$m_aClassParams[$sClass]["reconc_keys"]; + } + final static public function GetDisplayTemplate($sClass) + { + self::_check_subclass($sClass); + return self::$m_aClassParams[$sClass]["display_template"]; + } + final static public function GetAttributeOrigin($sClass, $sAttCode) + { + self::_check_subclass($sClass); + return self::$m_aAttribOrigins[$sClass][$sAttCode]; + } + final static public function GetPrequisiteAttributes($sClass, $sAttCode) + { + self::_check_subclass($sClass); + $oAtt = self::GetAttributeDef($sClass, $sAttCode); + // Temporary implementation: later, we might be able to compute + // the dependencies, based on the attributes definition + // (allowed values and default values) + if ($oAtt->IsWritable()) + { + return $oAtt->GetPrerequisiteAttributes(); + } + else + { + return array(); + } + } + // #@# restore to private ? + final static public function DBGetTable($sClass, $sAttCode = null) + { + self::_check_subclass($sClass); + if (empty($sAttCode) || ($sAttCode == "id")) + { + $sTableRaw = self::$m_aClassParams[$sClass]["db_table"]; + if (empty($sTableRaw)) + { + // return an empty string whenever the table is undefined, meaning that there is no table associated to this 'abstract' class + return ''; + } + else + { + return self::$m_sTablePrefix.$sTableRaw; + } + } + // This attribute has been inherited (compound objects) + return self::DBGetTable(self::$m_aAttribOrigins[$sClass][$sAttCode]); + } + + final static protected function DBEnumTables() + { + // This API do not rely on our capability to query the DB and retrieve + // the list of existing tables + // Rather, it uses the list of expected tables, corresponding to the data model + $aTables = array(); + foreach (self::GetClasses() as $sClass) + { + if (self::IsAbstract($sClass)) continue; + $sTable = self::DBGetTable($sClass); + + // Could be completed later with all the classes that are using a given table + if (!array_key_exists($sTable, $aTables)) + { + $aTables[$sTable] = array(); + } + $aTables[$sTable][] = $sClass; + } + return $aTables; + } + + final static public function DBGetKey($sClass) + { + self::_check_subclass($sClass); + return self::$m_aClassParams[$sClass]["db_key_field"]; + } + final static public function DBGetClassField($sClass) + { + self::_check_subclass($sClass); + return self::$m_aClassParams[$sClass]["db_finalclass_field"]; + } + final static public function HasFinalClassField($sClass) + { + self::_check_subclass($sClass); + if (!array_key_exists("db_finalclass_field", self::$m_aClassParams[$sClass])) return false; + return (self::$m_aClassParams[$sClass]["db_finalclass_field"]); + } + final static public function IsStandaloneClass($sClass) + { + self::_check_subclass($sClass); + + $sRootClass = self::GetRootClass($sClass); + return (!self::HasFinalClassField($sRootClass)); + } + final static public function IsParentClass($sParentClass, $sChildClass) + { + self::_check_subclass($sChildClass); + self::_check_subclass($sParentClass); + if (in_array($sParentClass, self::$m_aParentClasses[$sChildClass])) return true; + if ($sChildClass == $sParentClass) return true; + return false; + } + final static public function IsSameFamilyBranch($sClassA, $sClassB) + { + self::_check_subclass($sClassA); + self::_check_subclass($sClassB); + if (in_array($sClassA, self::$m_aParentClasses[$sClassB])) return true; + if (in_array($sClassB, self::$m_aParentClasses[$sClassA])) return true; + if ($sClassA == $sClassB) return true; + return false; + } + final static public function IsSameFamily($sClassA, $sClassB) + { + self::_check_subclass($sClassA); + self::_check_subclass($sClassB); + return (self::GetRootClass($sClassA) == self::GetRootClass($sClassB)); + } + + // Attributes of a given class may contain attributes defined in a parent class + // - Some attributes are a copy of the definition + // - Some attributes correspond to the upper class table definition (compound objects) + // (see also filters definition) + private static $m_aAttribDefs = array(); // array of ("classname" => array of attributes) + private static $m_aAttribOrigins = array(); // array of ("classname" => array of ("attcode"=>"sourceclass")) + private static $m_aExtKeyFriends = array(); // array of ("classname" => array of ("indirect ext key attcode"=> array of ("relative ext field"))) + final static public function ListAttributeDefs($sClass) + { + self::_check_subclass($sClass); + return self::$m_aAttribDefs[$sClass]; + } + + final public static function GetAttributesList($sClass) + { + self::_check_subclass($sClass); + return array_keys(self::$m_aAttribDefs[$sClass]); + } + + final public static function GetFiltersList($sClass) + { + self::_check_subclass($sClass); + return array_keys(self::$m_aFilterDefs[$sClass]); + } + + final public static function GetKeysList($sClass) + { + self::_check_subclass($sClass); + $aExtKeys = array(); + foreach(self::$m_aAttribDefs[$sClass] as $sAttCode => $oAttDef) + { + if ($oAttDef->IsExternalKey()) + { + $aExtKeys[] = $sAttCode; + } + } + return $aExtKeys; + } + + final static public function IsValidKeyAttCode($sClass, $sAttCode) + { + if (!array_key_exists($sClass, self::$m_aAttribDefs)) return false; + if (!array_key_exists($sAttCode, self::$m_aAttribDefs[$sClass])) return false; + return (self::$m_aAttribDefs[$sClass][$sAttCode]->IsExternalKey()); + } + final static public function IsValidAttCode($sClass, $sAttCode) + { + if (!array_key_exists($sClass, self::$m_aAttribDefs)) return false; + return (array_key_exists($sAttCode, self::$m_aAttribDefs[$sClass])); + } + final static public function IsAttributeOrigin($sClass, $sAttCode) + { + return (self::$m_aAttribOrigins[$sClass][$sAttCode] == $sClass); + } + + final static public function IsValidFilterCode($sClass, $sFilterCode) + { + if (!array_key_exists($sClass, self::$m_aFilterDefs)) return false; + return (array_key_exists($sFilterCode, self::$m_aFilterDefs[$sClass])); + } + public static function IsValidClass($sClass) + { + return (array_key_exists($sClass, self::$m_aAttribDefs)); + } + + public static function IsValidObject($oObject) + { + if (!is_object($oObject)) return false; + return (self::IsValidClass(get_class($oObject))); + } + + public static function IsReconcKey($sClass, $sAttCode) + { + return (in_array($sAttCode, self::GetReconcKeys($sClass))); + } + + final static public function GetAttributeDef($sClass, $sAttCode) + { + self::_check_subclass($sClass); + return self::$m_aAttribDefs[$sClass][$sAttCode]; + } + + final static public function GetExternalKeys($sClass) + { + $aExtKeys = array(); + foreach (self::ListAttributeDefs($sClass) as $sAttCode => $oAtt) + { + if ($oAtt->IsExternalKey()) + { + $aExtKeys[$sAttCode] = $oAtt; + } + } + return $aExtKeys; + } + + final static public function GetLinkedSets($sClass) + { + $aLinkedSets = array(); + foreach (self::ListAttributeDefs($sClass) as $sAttCode => $oAtt) + { + if (is_subclass_of($oAtt, 'AttributeLinkedSet')) + { + $aLinkedSets[$sAttCode] = $oAtt; + } + } + return $aLinkedSets; + } + + final static public function GetExternalFields($sClass, $sKeyAttCode) + { + $aExtFields = array(); + foreach (self::ListAttributeDefs($sClass) as $sAttCode => $oAtt) + { + if ($oAtt->IsExternalField() && ($oAtt->GetKeyAttCode() == $sKeyAttCode)) + { + $aExtFields[] = $oAtt; + } + } + return $aExtFields; + } + + final static public function GetExtKeyFriends($sClass, $sExtKeyAttCode) + { + if (array_key_exists($sExtKeyAttCode, self::$m_aExtKeyFriends[$sClass])) + { + return self::$m_aExtKeyFriends[$sClass][$sExtKeyAttCode]; + } + else + { + return array(); + } + } + + public static function GetLabel($sClass, $sAttCode) + { + $oAttDef = self::GetAttributeDef($sClass, $sAttCode); + if ($oAttDef) return $oAttDef->GetLabel(); + return ""; + } + + public static function GetDescription($sClass, $sAttCode) + { + $oAttDef = self::GetAttributeDef($sClass, $sAttCode); + if ($oAttDef) return $oAttDef->GetDescription(); + return ""; + } + + // Filters of a given class may contain filters defined in a parent class + // - Some filters are a copy of the definition + // - Some filters correspond to the upper class table definition (compound objects) + // (see also attributes definition) + private static $m_aFilterDefs = array(); // array of ("classname" => array filterdef) + private static $m_aFilterOrigins = array(); // array of ("classname" => array of ("attcode"=>"sourceclass")) + + public static function GetClassFilterDefs($sClass) + { + self::_check_subclass($sClass); + return self::$m_aFilterDefs[$sClass]; + } + + final static public function GetClassFilterDef($sClass, $sFilterCode) + { + self::_check_subclass($sClass); + return self::$m_aFilterDefs[$sClass][$sFilterCode]; + } + + public static function GetFilterLabel($sClass, $sFilterCode) + { + $oFilter = self::GetClassFilterDef($sClass, $sFilterCode); + if ($oFilter) return $oFilter->GetLabel(); + return ""; + } + + public static function GetFilterDescription($sClass, $sFilterCode) + { + $oFilter = self::GetClassFilterDef($sClass, $sFilterCode); + if ($oFilter) return $oFilter->GetDescription(); + return ""; + } + + // returns an array of opcode=>oplabel (e.g. "differs from") + public static function GetFilterOperators($sClass, $sFilterCode) + { + $oFilter = self::GetClassFilterDef($sClass, $sFilterCode); + if ($oFilter) return $oFilter->GetOperators(); + return array(); + } + + // returns an opcode + public static function GetFilterLooseOperator($sClass, $sFilterCode) + { + $oFilter = self::GetClassFilterDef($sClass, $sFilterCode); + if ($oFilter) return $oFilter->GetLooseOperator(); + return array(); + } + + public static function GetFilterOpDescription($sClass, $sFilterCode, $sOpCode) + { + $oFilter = self::GetClassFilterDef($sClass, $sFilterCode); + if ($oFilter) return $oFilter->GetOpDescription($sOpCode); + return ""; + } + + public static function GetFilterHTMLInput($sFilterCode) + { + return ""; + } + + // Lists of attributes/search filters + // + private static $m_aListInfos = array(); // array of ("listcode" => various info on the list, common to every classes) + private static $m_aListData = array(); // array of ("classname" => array of "listcode" => list) + // list may be an array of attcode / fltcode + // list may be an array of "groupname" => (array of attcode / fltcode) + + public static function EnumZLists() + { + return array_keys(self::$m_aListInfos); + } + + final static public function GetZListInfo($sListCode) + { + return self::$m_aListInfos[$sListCode]; + } + + public static function GetZListItems($sClass, $sListCode) + { + if (array_key_exists($sClass, self::$m_aListData)) + { + if (array_key_exists($sListCode, self::$m_aListData[$sClass])) + { + return self::$m_aListData[$sClass][$sListCode]; + } + } + $sParentClass = self::GetParentPersistentClass($sClass); + if (empty($sParentClass)) return array(); // nothing for the mother of all classes + // Dig recursively + return self::GetZListItems($sParentClass, $sListCode); + } + + public static function IsAttributeInZList($sClass, $sListCode, $sAttCodeOrFltCode, $sGroup = null) + { + $aZList = self::GetZListItems($sClass, $sListCode); + if (!$sGroup) + { + return (in_array($sAttCodeOrFltCode, $aZList)); + } + return (in_array($sAttCodeOrFltCode, $aZList[$sGroup])); + } + + // + // Relations + // + private static $m_aRelationInfos = array(); // array of ("relcode" => various info on the list, common to every classes) + + public static function EnumRelations() + { + return array_keys(self::$m_aRelationInfos); + } + + public static function EnumRelationProperties($sRelCode) + { + MyHelpers::CheckKeyInArray('relation code', $sRelCode, self::$m_aRelationInfos); + return self::$m_aRelationInfos[$sRelCode]; + } + + final static public function GetRelationProperty($sRelCode, $sProperty) + { + MyHelpers::CheckKeyInArray('relation code', $sRelCode, self::$m_aRelationInfos); + MyHelpers::CheckKeyInArray('relation property', $sProperty, self::$m_aRelationInfos[$sRelCode]); + + return self::$m_aRelationInfos[$sRelCode][$sProperty]; + } + + public static function EnumRelationQueries($sClass, $sRelCode) + { + MyHelpers::CheckKeyInArray('relation code', $sRelCode, self::$m_aRelationInfos); + return call_user_func_array(array($sClass, 'GetRelationQueries'), array($sRelCode)); + } + + // + // Object lifecycle model + // + private static $m_aStates = array(); // array of ("classname" => array of "statecode"=>array('label'=>..., 'description'=>..., attribute_inherit=> attribute_list=>...)) + private static $m_aStimuli = array(); // array of ("classname" => array of ("stimuluscode"=>array('label'=>..., 'description'=>...))) + private static $m_aTransitions = array(); // array of ("classname" => array of ("statcode_from"=>array of ("stimuluscode" => array('target_state'=>..., 'actions'=>array of handlers procs, 'user_restriction'=>TBD))) + + public static function EnumStates($sClass) + { + if (array_key_exists($sClass, self::$m_aStates)) + { + return self::$m_aStates[$sClass]; + } + else + { + return array(); + } + } + + public static function EnumStimuli($sClass) + { + if (array_key_exists($sClass, self::$m_aStimuli)) + { + return self::$m_aStimuli[$sClass]; + } + else + { + return array(); + } + } + + public static function EnumTransitions($sClass, $sStateCode) + { + if (array_key_exists($sClass, self::$m_aTransitions)) + { + if (array_key_exists($sStateCode, self::$m_aTransitions[$sClass])) + { + return self::$m_aTransitions[$sClass][$sStateCode]; + } + } + return array(); + } + public static function GetAttributeFlags($sClass, $sState, $sAttCode) + { + $iFlags = 0; // By default (if no life cycle) no flag at all + $sStateAttCode = self::GetStateAttributeCode($sClass); + if (!empty($sStateAttCode)) + { + $aStates = MetaModel::EnumStates($sClass); + if (!array_key_exists($sState, $aStates)) + { + throw new CoreException("Invalid state '$sState' for class '$sClass', expecting a value in {".implode(', ', array_keys($aStates))."}"); + } + $aCurrentState = $aStates[$sState]; + if ( (array_key_exists('attribute_list', $aCurrentState)) && (array_key_exists($sAttCode, $aCurrentState['attribute_list'])) ) + { + $iFlags = $aCurrentState['attribute_list'][$sAttCode]; + } + } + return $iFlags; + } + + // + // Allowed values + // + + public static function GetAllowedValues_att($sClass, $sAttCode, $aArgs = array(), $sBeginsWith = '') + { + $oAttDef = self::GetAttributeDef($sClass, $sAttCode); + return $oAttDef->GetAllowedValues($aArgs, $sBeginsWith); + } + + public static function GetAllowedValues_flt($sClass, $sFltCode, $aArgs = array(), $sBeginsWith = '') + { + $oFltDef = self::GetClassFilterDef($sClass, $sFltCode); + return $oFltDef->GetAllowedValues($aArgs, $sBeginsWith); + } + + // + // Businezz model declaration verbs (should be static) + // + + public static function RegisterZList($sListCode, $aListInfo) + { + // Check mandatory params + $aMandatParams = array( + "description" => "detailed (though one line) description of the list", + "type" => "attributes | filters", + ); + foreach($aMandatParams as $sParamName=>$sParamDesc) + { + if (!array_key_exists($sParamName, $aListInfo)) + { + throw new CoreException("Declaration of list $sListCode - missing parameter $sParamName"); + } + } + + self::$m_aListInfos[$sListCode] = $aListInfo; + } + + public static function RegisterRelation($sRelCode, $aRelationInfo) + { + // Check mandatory params + $aMandatParams = array( + "description" => "detailed (though one line) description of the list", + "verb_down" => "e.g.: 'impacts'", + "verb_up" => "e.g.: 'is impacted by'", + ); + foreach($aMandatParams as $sParamName=>$sParamDesc) + { + if (!array_key_exists($sParamName, $aRelationInfo)) + { + throw new CoreException("Declaration of relation $sRelCode - missing parameter $sParamName"); + } + } + + self::$m_aRelationInfos[$sRelCode] = $aRelationInfo; + } + + // Must be called once and only once... + public static function InitClasses($sTablePrefix) + { + if (count(self::GetClasses()) > 0) + { + throw new CoreException("InitClasses should not be called more than once -skipped"); + return; + } + + self::$m_sTablePrefix = $sTablePrefix; + + foreach(get_declared_classes() as $sPHPClass) { + if (is_subclass_of($sPHPClass, 'DBObject')) + { + if (method_exists($sPHPClass, 'Init')) + { + call_user_func(array($sPHPClass, 'Init')); + } + } + } + foreach (self::GetClasses() as $sClass) + { + // Compute the fields that will be used to display a pointer to another object + // + self::$m_aExtKeyFriends[$sClass] = array(); + foreach (self::$m_aAttribDefs[$sClass] as $sAttCode => $oAttDef) + { + if ($oAttDef->IsExternalKey(EXTKEY_ABSOLUTE)) + { + // oAttDef is either + // - an external KEY / FIELD (direct), + // - an external field pointing to an external KEY / FIELD + // - an external field pointing to an external field pointing to.... + + // Get the real external key attribute + // It will be our reference to determine the other ext fields related to the same ext key + $oFinalKeyAttDef = $oAttDef->GetKeyAttDef(EXTKEY_ABSOLUTE); + + self::$m_aExtKeyFriends[$sClass][$sAttCode] = array(); + foreach (self::GetExternalFields($sClass, $oAttDef->GetKeyAttCode($sAttCode)) as $oExtField) + { + // skip : those extfields will be processed as external keys + if ($oExtField->IsExternalKey(EXTKEY_ABSOLUTE)) continue; + + // Note: I could not compare the objects by the mean of '===' + // because they are copied for the inheritance, and the internal references are NOT updated + if ($oExtField->GetKeyAttDef(EXTKEY_ABSOLUTE) == $oFinalKeyAttDef) + { + self::$m_aExtKeyFriends[$sClass][$sAttCode][$oExtField->GetCode()] = $oExtField; + } + } + } + } + + // Add a 'id' filter + // + if (array_key_exists('id', self::$m_aAttribDefs[$sClass])) + { + throw new CoreException("Class $sClass, 'id' is a reserved keyword, it cannot be used as an attribute code"); + } + if (array_key_exists('id', self::$m_aFilterDefs[$sClass])) + { + throw new CoreException("Class $sClass, 'id' is a reserved keyword, it cannot be used as a filter code"); + } + $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 + // + if (!self::IsStandaloneClass($sClass)) + { + if (array_key_exists('finalclass', self::$m_aAttribDefs[$sClass])) + { + throw new CoreException("Class $sClass, 'finalclass' is a reserved keyword, it cannot be used as an attribute code"); + } + if (array_key_exists('finalclass', self::$m_aFilterDefs[$sClass])) + { + throw new CoreException("Class $sClass, 'finalclass' is a reserved keyword, it cannot be used as a filter code"); + } + $sClassAttCode = 'finalclass'; + $sRootClass = self::GetRootClass($sClass); + $sDbFinalClassField = self::DBGetClassField($sRootClass); + $oClassAtt = new AttributeClass($sClassAttCode, array( + "label"=>"Class", + "description"=>"Real (final) object class", + "class_category"=>null, + "more_values"=>'', + "sql"=>$sDbFinalClassField, + "default_value"=>$sClass, + "is_null_allowed"=>false, + "depends_on"=>array() + )); + self::$m_aAttribDefs[$sClass][$sClassAttCode] = $oClassAtt; + self::$m_aAttribOrigins[$sClass][$sClassAttCode] = $sRootClass; + + $oClassFlt = new FilterFromAttribute($oClassAtt); + self::$m_aFilterDefs[$sClass][$sClassAttCode] = $oClassFlt; + self::$m_aFilterOrigins[$sClass][$sClassAttCode] = self::GetRootClass($sClass); + } + + // Define defaults values for the standard ZLists + // + //foreach (self::$m_aListInfos as $sListCode => $aListConfig) + //{ + // if (!isset(self::$m_aListData[$sClass][$sListCode])) + // { + // $aAllAttributes = array_keys(self::$m_aAttribDefs[$sClass]); + // self::$m_aListData[$sClass][$sListCode] = $aAllAttributes; + // //echo "

$sClass: $sListCode (".count($aAllAttributes)." attributes)

\n"; + // } + //} + } + + } + + // To be overriden, must be called for any object class (optimization) + public static function Init() + { + // In fact it is an ABSTRACT function, but this is not compatible with the fact that it is STATIC (error in E_STRICT interpretation) + } + // To be overloaded by biz model declarations + public static function GetRelationQueries($sRelCode) + { + // In fact it is an ABSTRACT function, but this is not compatible with the fact that it is STATIC (error in E_STRICT interpretation) + return array(); + } + + public static function Init_Params($aParams) + { + // Check mandatory params + $aMandatParams = array( + "category" => "group classes by modules defining their visibility in the UI", + "name" => "internal class name, may be different than the PHP class name", + "description" => "detailed (though one line) description of the class", + "key_type" => "autoincrement | string", + "key_label" => "if set, then display the key as an attribute", + "name_attcode" => "define wich attribute is the class name, may be an inherited attribute", + "state_attcode" => "define wich attribute is representing the state (object lifecycle)", + "reconc_keys" => "define the attributes that will 'almost uniquely' identify an object in batch processes", + "db_table" => "database table", + "db_key_field" => "database field which is the key", + "db_finalclass_field" => "database field wich is the reference to the actual class of the object, considering that this will be a compound class", + ); + + $sClass = self::GetCallersPHPClass("Init"); + if (!array_key_exists("name", $aParams)) + { + throw new CoreException("Declaration of class $sClass: missing name ({$aMandatParams["name"]})"); + } + + foreach($aMandatParams as $sParamName=>$sParamDesc) + { + if (!array_key_exists($sParamName, $aParams)) + { + throw new CoreException("Declaration of class $sClass - missing parameter $sParamName"); + } + } + + $aCategories = explode(',', $aParams['category']); + foreach ($aCategories as $sCategory) + { + self::$m_Category2Class[$sCategory][] = $sClass; + } + self::$m_Category2Class[''][] = $sClass; // all categories, include this one + + + self::$m_aRootClasses[$sClass] = $sClass; // first, let consider that I am the root... updated on inheritance + self::$m_aParentClasses[$sClass] = array(); + self::$m_aChildClasses[$sClass] = array(); + + self::$m_aClassParams[$sClass]= $aParams; + + self::$m_aAttribDefs[$sClass] = array(); + self::$m_aAttribOrigins[$sClass] = array(); + self::$m_aExtKeyFriends[$sClass] = array(); + self::$m_aFilterDefs[$sClass] = array(); + self::$m_aFilterOrigins[$sClass] = array(); + } + + protected static function object_array_mergeclone($aSource1, $aSource2) + { + $aRes = array(); + foreach ($aSource1 as $key=>$object) + { + $aRes[$key] = clone $object; + } + foreach ($aSource2 as $key=>$object) + { + $aRes[$key] = clone $object; + } + return $aRes; + } + + public static function Init_InheritAttributes($sSourceClass = null) + { + $sTargetClass = self::GetCallersPHPClass("Init"); + if (empty($sSourceClass)) + { + // Default: inherit from parent class + $sSourceClass = self::GetParentPersistentClass($sTargetClass); + if (empty($sSourceClass)) return; // no attributes for the mother of all classes + } + if (isset(self::$m_aAttribDefs[$sSourceClass])) + { + if (!isset(self::$m_aAttribDefs[$sTargetClass])) + { + self::$m_aAttribDefs[$sTargetClass] = array(); + self::$m_aAttribOrigins[$sTargetClass] = array(); + } + self::$m_aAttribDefs[$sTargetClass] = self::object_array_mergeclone(self::$m_aAttribDefs[$sTargetClass], self::$m_aAttribDefs[$sSourceClass]); + self::$m_aAttribOrigins[$sTargetClass] = array_merge(self::$m_aAttribOrigins[$sTargetClass], self::$m_aAttribOrigins[$sSourceClass]); + } + // later on, we might consider inheritance in different ways !!! + //if (strlen(self::DBGetTable($sSourceClass)) != 0) + if (self::HasFinalClassField(self::$m_aRootClasses[$sSourceClass])) + { + // Inherit the root class + self::$m_aRootClasses[$sTargetClass] = self::$m_aRootClasses[$sSourceClass]; + } + else + { + // I am a root class, standalone as well ! + // ???? + //self::$m_aRootClasses[$sTargetClass] = $sTargetClass; + } + self::$m_aParentClasses[$sTargetClass] += self::$m_aParentClasses[$sSourceClass]; + self::$m_aParentClasses[$sTargetClass][] = $sSourceClass; + // I am the child of each and every parent... + foreach(self::$m_aParentClasses[$sTargetClass] as $sAncestorClass) + { + self::$m_aChildClasses[$sAncestorClass][] = $sTargetClass; + } + } + public static function Init_OverloadAttributeParams($sAttCode, $aParams) + { + $sTargetClass = self::GetCallersPHPClass("Init"); + + if (!self::IsValidAttCode($sTargetClass, $sAttCode)) + { + throw new CoreException("Could not overload '$sAttCode', expecting a code from {".implode(", ", self::GetAttributesList($sTargetClass))."}"); + } + self::$m_aAttribDefs[$sTargetClass][$sAttCode]->OverloadParams($aParams); + } + public static function Init_AddAttribute(AttributeDefinition $oAtt) + { + $sTargetClass = self::GetCallersPHPClass("Init"); + self::$m_aAttribDefs[$sTargetClass][$oAtt->GetCode()] = $oAtt; + self::$m_aAttribOrigins[$sTargetClass][$oAtt->GetCode()] = $sTargetClass; + // Note: it looks redundant to put targetclass there, but a mix occurs when inheritance is used + + // Specific case of external fields: + // I wanted to simplify the syntax of the declaration of objects in the biz model + // Therefore, the reference to the host class is set there + $oAtt->SetHostClass($sTargetClass); + } + + public static function Init_InheritFilters($sSourceClass = null) + { + $sTargetClass = self::GetCallersPHPClass("Init"); + if (empty($sSourceClass)) + { + // Default: inherit from parent class + $sSourceClass = self::GetParentPersistentClass($sTargetClass); + if (empty($sSourceClass)) return; // no filters for the mother of all classes + } + if (isset(self::$m_aFilterDefs[$sSourceClass])) + { + if (!isset(self::$m_aFilterDefs[$sTargetClass])) + { + self::$m_aFilterDefs[$sTargetClass] = array(); + self::$m_aFilterOrigins[$sTargetClass] = array(); + } + + foreach (self::$m_aFilterDefs[$sSourceClass] as $sFltCode=>$oFilter) + { + if ($oFilter instanceof FilterFromAttribute) + { + // In that case, cloning is not enough: + // we must ensure that we will point to the correct + // attribute definition (in case some properties are overloaded) + $oAttDef1 = $oFilter->__GetRefAttribute(); + $oAttDef2 = self::GetAttributeDef($sTargetClass, $oAttDef1->GetCode()); + $oNewFilter = new FilterFromAttribute($oAttDef2); + } + else + { + $oNewFilter = clone $oFilter; + } + self::$m_aFilterDefs[$sTargetClass][$sFltCode] = $oNewFilter; + } + + self::$m_aFilterOrigins[$sTargetClass] = array_merge(self::$m_aFilterOrigins[$sTargetClass], self::$m_aFilterOrigins[$sSourceClass]); + } + } + + public static function Init_OverloadFilterParams($sFltCode, $aParams) + { + $sTargetClass = self::GetCallersPHPClass("Init"); + + if (!self::IsValidFilterCode($sTargetClass, $sFltCode)) + { + throw new CoreException("Could not overload '$sFltCode', expecting a code from {".implode(", ", self::GetFiltersList($sTargetClass))."}"); + } + self::$m_aFilterDefs[$sTargetClass][$sFltCode]->OverloadParams($aParams); + } + + public static function Init_AddFilter(FilterDefinition $oFilter) + { + $sTargetClass = self::GetCallersPHPClass("Init"); + self::$m_aFilterDefs[$sTargetClass][$oFilter->GetCode()] = $oFilter; + self::$m_aFilterOrigins[$sTargetClass][$oFilter->GetCode()] = $sTargetClass; + // Note: it looks redundant to put targetclass there, but a mix occurs when inheritance is used + } + public static function Init_AddFilterFromAttribute($sAttCode) + { + $sTargetClass = self::GetCallersPHPClass("Init"); + + $oAttDef = self::GetAttributeDef($sTargetClass, $sAttCode); + + $sFilterCode = $sAttCode; + $oNewFilter = new FilterFromAttribute($oAttDef); + self::$m_aFilterDefs[$sTargetClass][$sFilterCode] = $oNewFilter; + + if ($oAttDef->IsExternalField()) + { + $sKeyAttCode = $oAttDef->GetKeyAttCode(); + $oKeyDef = self::GetAttributeDef($sTargetClass, $sKeyAttCode); + self::$m_aFilterOrigins[$sTargetClass][$sFilterCode] = $oKeyDef->GetTargetClass(); + } + else + { + self::$m_aFilterOrigins[$sTargetClass][$sFilterCode] = $sTargetClass; + } + // Note: it looks redundant to put targetclass there, but a mix occurs when inheritance is used + } + + public static function Init_SetZListItems($sListCode, $aItems) + { + MyHelpers::CheckKeyInArray('list code', $sListCode, self::$m_aListInfos); + + $sTargetClass = self::GetCallersPHPClass("Init"); + self::$m_aListData[$sTargetClass][$sListCode] = $aItems; + } + + public static function Init_DefineState($sStateCode, $aStateDef) + { + $sTargetClass = self::GetCallersPHPClass("Init"); + if (is_null($aStateDef['attribute_list'])) $aStateDef['attribute_list'] = array(); + + $sParentState = $aStateDef['attribute_inherit']; + if (!empty($sParentState)) + { + // Inherit from the given state (must be defined !) + $aToInherit = self::$m_aStates[$sTargetClass][$sParentState]; + // The inherited configuration could be overriden + $aStateDef['attribute_list'] = array_merge($aToInherit, $aStateDef['attribute_list']); + } + self::$m_aStates[$sTargetClass][$sStateCode] = $aStateDef; + + // by default, create an empty set of transitions associated to that state + self::$m_aTransitions[$sTargetClass][$sStateCode] = array(); + } + + public static function Init_DefineStimulus($sStimulusCode, $oStimulus) + { + $sTargetClass = self::GetCallersPHPClass("Init"); + self::$m_aStimuli[$sTargetClass][$sStimulusCode] = $oStimulus; + } + + public static function Init_DefineTransition($sStateCode, $sStimulusCode, $aTransitionDef) + { + $sTargetClass = self::GetCallersPHPClass("Init"); + if (is_null($aTransitionDef['actions'])) $aTransitionDef['actions'] = array(); + self::$m_aTransitions[$sTargetClass][$sStateCode][$sStimulusCode] = $aTransitionDef; + } + + public static function Init_InheritLifecycle($sSourceClass = '') + { + $sTargetClass = self::GetCallersPHPClass("Init"); + if (empty($sSourceClass)) + { + // Default: inherit from parent class + $sSourceClass = self::GetParentPersistentClass($sTargetClass); + if (empty($sSourceClass)) return; // no attributes for the mother of all classes + } + + self::$m_aClassParams[$sTargetClass]["state_attcode"] = self::$m_aClassParams[$sSourceClass]["state_attcode"]; + self::$m_aStates[$sTargetClass] = clone self::$m_aStates[$sSourceClass]; + self::$m_aStimuli[$sTargetClass] = clone self::$m_aStimuli[$sSourceClass]; + self::$m_aTransitions[$sTargetClass] = clone self::$m_aTransitions[$sSourceClass]; + } + + // + // Static API + // + + public static function GetRootClass($sClass = null) + { + self::_check_subclass($sClass); + return self::$m_aRootClasses[$sClass]; + } + public static function IsRootClass($sClass) + { + self::_check_subclass($sClass); + return (self::GetRootClass($sClass) == $sClass); + } + public static function EnumParentClasses($sClass) + { + self::_check_subclass($sClass); + return self::$m_aParentClasses[$sClass]; + } + public static function EnumChildClasses($sClass, $iOption = ENUM_CHILD_CLASSES_EXCLUDETOP) + { + self::_check_subclass($sClass); + + $aRes = self::$m_aChildClasses[$sClass]; + if ($iOption != ENUM_CHILD_CLASSES_EXCLUDETOP) + { + // Add it to the list + $aRes[] = $sClass; + } + return $aRes; + } + + public static function EnumCategories() + { + return array_keys(self::$m_Category2Class); + } + + // Note: use EnumChildClasses to take the compound objects into account + public static function GetSubclasses($sClass) + { + self::_check_subclass($sClass); + $aSubClasses = array(); + foreach(get_declared_classes() as $sSubClass) { + if (is_subclass_of($sSubClass, $sClass)) + { + $aSubClasses[] = $sSubClass; + } + } + return $aSubClasses; + } + public static function GetClasses($sCategory = '') + { + if (array_key_exists($sCategory, self::$m_Category2Class)) + { + return self::$m_Category2Class[$sCategory]; + } + + if (count(self::$m_Category2Class) > 0) + { + throw new CoreException("unkown class category '$sCategory', expecting a value in {".implode(', ', array_keys(self::$m_Category2Class))."}"); + } + return array(); + } + + public static function IsAbstract($sClass) + { + if (strlen(self::DBGetTable($sClass)) == 0) return true; + return false; + } + + protected static $m_aQueryStructCache = array(); + + protected static function PrepareQueryArguments($aArgs) + { + // Translate any object into scalars + // + $aScalarArgs = array(); + foreach($aArgs as $sArgName => $value) + { + if (self::IsValidObject($value)) + { + $aScalarArgs = array_merge($aScalarArgs, $value->ToArgs($sArgName)); + } + else + { + $aScalarArgs[$sArgName] = (string) $value; + } + } + // Add standard contextual arguments + // + $aScalarArgs['current_contact_id'] = UserRights::GetContactId(); + + return $aScalarArgs; + } + + public static function MakeSelectQuery(DBObjectSearch $oFilter, $aOrderBy = array(), $aArgs = array()) + { + // Query caching + // + $bQueryCacheEnabled = true; + $aParams = array(); + $sOqlQuery = $oFilter->ToOql($aParams); // Render with arguments in clear + if ($bQueryCacheEnabled) + { + if (array_key_exists($sOqlQuery, self::$m_aQueryStructCache)) + { + // hit! + $oSelect = clone self::$m_aQueryStructCache[$sOqlQuery]; + } + } + + if (!isset($oSelect)) + { + $aTranslation = array(); + $aClassAliases = array(); + $aTableAliases = array(); + $oConditionTree = $oFilter->GetCriteria(); + + $oSelect = self::MakeQuery($oFilter->GetSelectedClasses(), $oConditionTree, $aClassAliases, $aTableAliases, $aTranslation, $oFilter); + + self::$m_aQueryStructCache[$sOqlQuery] = clone $oSelect; + } + + // Check the order by specification, and prefix with the class alias + // + $aOrderSpec = array(); + foreach ($aOrderBy as $sFieldAlias => $bAscending) + { + MyHelpers::CheckValueInArray('field name in ORDER BY spec', $sFieldAlias, self::GetAttributesList($oFilter->GetClass())); + if (!is_bool($bAscending)) + { + throw new CoreException("Wrong direction in ORDER BY spec, found '$bAscending' and expecting a boolean value"); + } + $aOrderSpec[$oFilter->GetClassAlias().$sFieldAlias] = $bAscending; + } + // By default, force the name attribute to be the ordering key + // + if (empty($aOrderSpec)) + { + $sNameAttCode = self::GetNameAttributeCode($oFilter->GetClass()); + if (!empty($sNameAttCode)) + { + // By default, simply order on the "name" attribute, ascending + $aOrderSpec[$oFilter->GetClassAlias().$sNameAttCode] = true; + } + } + + // Go + // + $aScalarArgs = array_merge(self::PrepareQueryArguments($aArgs), $oFilter->GetInternalParams()); + + try + { + $sRes = $oSelect->RenderSelect($aOrderSpec, $aScalarArgs); + } + catch (MissingQueryArgument $e) + { + // Add some information... + $e->addInfo('OQL', $sOqlQuery); + throw $e; + } + + if (self::$m_bTraceQueries) + { + $aParams = array(); + if (!array_key_exists($sOqlQuery, self::$m_aQueriesLog)) + { + self::$m_aQueriesLog[$sOqlQuery] = array( + 'sql' => array(), + 'count' => 0, + ); + } + self::$m_aQueriesLog[$sOqlQuery]['count']++; + self::$m_aQueriesLog[$sOqlQuery]['sql'][] = $sRes; + } + + return $sRes; + } + + public static function ShowQueryTrace() + { + $iTotal = 0; + foreach (self::$m_aQueriesLog as $sOql => $aQueryData) + { + echo "

$sOql

\n"; + $iTotal += $aQueryData['count']; + echo '

'.$aQueryData['count'].'

'; + echo '

Example: '.$aQueryData['sql'][0].'

'; + } + echo "

Total

\n"; + echo "

Count of executed queries: $iTotal

"; + echo "

Count of built queries: ".count(self::$m_aQueriesLog)."

"; + } + + public static function MakeDeleteQuery(DBObjectSearch $oFilter, $aArgs = array()) + { + $aTranslation = array(); + $aClassAliases = array(); + $aTableAliases = array(); + $oConditionTree = $oFilter->GetCriteria(); + $oSelect = self::MakeQuery($oFilter->GetSelectedClasses(), $oConditionTree, $aClassAliases, $aTableAliases, $aTranslation, $oFilter); + $aScalarArgs = array_merge(self::PrepareQueryArguments($aArgs), $oFilter->GetInternalParams()); + return $oSelect->RenderDelete($aScalarArgs); + } + + public static function MakeUpdateQuery(DBObjectSearch $oFilter, $aValues, $aArgs = array()) + { + // $aValues is an array of $sAttCode => $value + $aTranslation = array(); + $aClassAliases = array(); + $aTableAliases = array(); + $oConditionTree = $oFilter->GetCriteria(); + $oSelect = self::MakeQuery($oFilter->GetSelectedClasses(), $oConditionTree, $aClassAliases, $aTableAliases, $aTranslation, $oFilter, array(), $aValues); + $aScalarArgs = array_merge(self::PrepareQueryArguments($aArgs), $oFilter->GetInternalParams()); + return $oSelect->RenderUpdate($aScalarArgs); + } + + private static function MakeQuery($aSelectedClasses, &$oConditionTree, &$aClassAliases, &$aTableAliases, &$aTranslation, DBObjectSearch $oFilter, $aExpectedAtts = array(), $aValues = array()) + { + // Note: query class might be different than the class of the filter + // -> this occurs when we are linking our class to an external class (referenced by, or pointing to) + // $aExpectedAtts is an array of sAttCode=>array of columns + $sClass = $oFilter->GetClass(); + $sClassAlias = $oFilter->GetClassAlias(); + + $bIsOnQueriedClass = array_key_exists($sClassAlias, $aSelectedClasses); + if ($bIsOnQueriedClass) + { + $aClassAliases = array_merge($aClassAliases, $oFilter->GetJoinedClasses()); + } + + self::DbgTrace("Entering: ".$oFilter->ToSibuSQL().", ".($bIsOnQueriedClass ? "MAIN" : "SECONDARY").", expectedatts=".count($aExpectedAtts).": ".implode(",", array_keys($aExpectedAtts))); + + $sRootClass = self::GetRootClass($sClass); + $sKeyField = self::DBGetKey($sClass); + + if ($bIsOnQueriedClass) + { + // default to the whole list of attributes + the very std id/finalclass + $aExpectedAtts['id'][] = $sClassAlias.'id'; + foreach (self::GetAttributesList($sClass) as $sAttCode) + { + $aExpectedAtts[$sAttCode][] = $sClassAlias.$sAttCode; // alias == class and attcode + } + } + + // Compute a clear view of external keys, and external attributes + // Build the list of external keys: + // -> ext keys required by a closed join ??? + // -> ext keys mentionned in a 'pointing to' condition + // -> ext keys required for an external field + // + $aExtKeys = array(); // array of sTableClass => array of (sAttCode (keys) => array of (sAttCode (fields)=> oAttDef)) + // + // Optimization: could be computed once for all (cached) + // Could be done in MakeQuerySingleTable ??? + // + + if ($bIsOnQueriedClass) + { + // Get all Ext keys for the queried class (??) + foreach(self::GetKeysList($sClass) as $sKeyAttCode) + { + $sKeyTableClass = self::$m_aAttribOrigins[$sClass][$sKeyAttCode]; + $aExtKeys[$sKeyTableClass][$sKeyAttCode] = array(); + } + } + // Get all Ext keys used by the filter + foreach ($oFilter->GetCriteria_PointingTo() as $sKeyAttCode => $trash) + { + $sKeyTableClass = self::$m_aAttribOrigins[$sClass][$sKeyAttCode]; + $aExtKeys[$sKeyTableClass][$sKeyAttCode] = array(); + } + // Add the ext fields used in the select (eventually adds an external key) + foreach(self::ListAttributeDefs($sClass) as $sAttCode=>$oAttDef) + { + if ($oAttDef->IsExternalField()) + { + $sKeyAttCode = $oAttDef->GetKeyAttCode(); + if (array_key_exists($sAttCode, $aExpectedAtts) || $oConditionTree->RequiresField($sClassAlias, $sAttCode)) + { + // Add the external attribute + $sKeyTableClass = self::$m_aAttribOrigins[$sClass][$sKeyAttCode]; + $aExtKeys[$sKeyTableClass][$sKeyAttCode][$sAttCode] = $oAttDef; + } + } + } + + // First query built upon on the leaf (ie current) class + // + self::DbgTrace("Main (=leaf) class, call MakeQuerySingleTable()"); + $oSelectBase = self::MakeQuerySingleTable($aSelectedClasses, $oConditionTree, $aClassAliases, $aTableAliases, $aTranslation, $oFilter, $sClass, $aExpectedAtts, $aExtKeys, $aValues); + + // Then we join the queries of the eventual parent classes (compound model) + foreach(self::EnumParentClasses($sClass) as $sParentClass) + { + if (self::DBGetTable($sParentClass) == "") continue; + self::DbgTrace("Parent class: $sParentClass... let's call MakeQuerySingleTable()"); + $oSelectParentTable = self::MakeQuerySingleTable($aSelectedClasses, $oConditionTree, $aClassAliases, $aTableAliases, $aTranslation, $oFilter, $sParentClass, $aExpectedAtts, $aExtKeys, $aValues); + $oSelectBase->AddInnerJoin($oSelectParentTable, $sKeyField, self::DBGetKey($sParentClass)); + } + + // Filter on objects referencing me + foreach ($oFilter->GetCriteria_ReferencedBy() as $sForeignClass => $aKeysAndFilters) + { + foreach ($aKeysAndFilters as $sForeignKeyAttCode => $oForeignFilter) + { + $oForeignKeyAttDef = self::GetAttributeDef($sForeignClass, $sForeignKeyAttCode); + + // We don't want any attribute from the foreign class, just filter on an inner join + $aExpAtts = array(); + + self::DbgTrace("Referenced by foreign key: $sForeignKeyAttCode... let's call MakeQuery()"); + //self::DbgTrace($oForeignFilter); + //self::DbgTrace($oForeignFilter->ToSibuSQL()); + //self::DbgTrace($oSelectForeign); + //self::DbgTrace($oSelectForeign->RenderSelect(array())); + $oSelectForeign = self::MakeQuery($aSelectedClasses, $oConditionTree, $aClassAliases, $aTableAliases, $aTranslation, $oForeignFilter, $aExpAtts); + + $sForeignClassAlias = $oForeignFilter->GetClassAlias(); + $sForeignKeyTable = $aTranslation[$sForeignClassAlias][$sForeignKeyAttCode][0]; + $sForeignKeyColumn = $aTranslation[$sForeignClassAlias][$sForeignKeyAttCode][1]; + $oSelectBase->AddInnerJoin($oSelectForeign, $sKeyField, $sForeignKeyColumn, $sForeignKeyTable); + } + } + + // Filter on related objects + // + foreach ($oFilter->GetCriteria_RelatedTo() as $aCritInfo) + { + $oSubFilter = $aCritInfo['flt']; + $sRelCode = $aCritInfo['relcode']; + $iMaxDepth = $aCritInfo['maxdepth']; + + // Get the starting point objects + $oStartSet = new CMDBObjectSet($oSubFilter); + + // Get the objects related to those objects... recursively... + $aRelatedObjs = $oStartSet->GetRelatedObjects($sRelCode, $iMaxDepth); + $aRestriction = array_key_exists($sRootClass, $aRelatedObjs) ? $aRelatedObjs[$sRootClass] : array(); + + // #@# todo - related objects and expressions... + // Create condition + if (count($aRestriction) > 0) + { + $oSelectBase->AddCondition($sKeyField.' IN ('.implode(', ', CMDBSource::Quote(array_keys($aRestriction), true)).')'); + } + else + { + // Quick N'dirty -> generate an empty set + $oSelectBase->AddCondition('false'); + } + } + + // Translate the conditions... and go + // + if ($bIsOnQueriedClass) + { + $oConditionTranslated = $oConditionTree->Translate($aTranslation); + $oSelectBase->SetCondition($oConditionTranslated); + } + + // That's all... cross fingers and we'll get some working query + + //MyHelpers::var_dump_html($oSelectBase, true); + //MyHelpers::var_dump_html($oSelectBase->RenderSelect(), true); + if (self::$m_bDebugQuery) $oSelectBase->DisplayHtml(); + return $oSelectBase; + } + + protected static function MakeQuerySingleTable($aSelectedClasses, &$oConditionTree, &$aClassAliases, &$aTableAliases, &$aTranslation, $oFilter, $sTableClass, $aExpectedAtts, $aExtKeys, $aValues) + { + // $aExpectedAtts is an array of sAttCode=>sAlias + // $aExtKeys is an array of sTableClass => array of (sAttCode (keys) => array of sAttCode (fields)) + + // Prepare the query for a single table (compound objects) + // Ignores the items (attributes/filters) that are not on the target table + // Perform an (inner or left) join for every external key (and specify the expected fields) + // + // Returns an SQLQuery + // + $sTargetClass = $oFilter->GetClass(); + $sTargetAlias = $oFilter->GetClassAlias(); + $sTable = self::DBGetTable($sTableClass); + $sTableAlias = self::GenerateUniqueAlias($aTableAliases, $sTargetAlias.'_'.$sTable, $sTable); + + $bIsOnQueriedClass = array_key_exists($sTargetAlias, $aSelectedClasses); + + self::DbgTrace("Entering: tableclass=$sTableClass, filter=".$oFilter->ToSibuSQL().", ".($bIsOnQueriedClass ? "MAIN" : "SECONDARY").", expectedatts=".count($aExpectedAtts).": ".implode(",", array_keys($aExpectedAtts))); + + // 1 - SELECT and UPDATE + // + // Note: no need for any values nor fields for foreign Classes (ie not the queried Class) + // + $aSelect = array(); + $aUpdateValues = array(); + + // 1/a - Get the key + // + if ($bIsOnQueriedClass) + { + $aSelect[$aExpectedAtts['id'][0]] = 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]['id'] = array($sTableAlias, self::DBGetKey($sTableClass)); + } + + // 1/b - Get the other attributes + // + foreach(self::ListAttributeDefs($sTableClass) as $sAttCode=>$oAttDef) + { + // Skip this attribute if not defined in this table + if (self::$m_aAttribOrigins[$sTargetClass][$sAttCode] != $sTableClass) continue; + + // Skip this attribute if not writable (means that it does not correspond + if (count($oAttDef->GetSQLExpressions()) == 0) continue; + + // Update... + // + if ($bIsOnQueriedClass && array_key_exists($sAttCode, $aValues)) + { + assert ($oAttDef->IsDirectField()); + foreach ($oAttDef->GetSQLValues($aValues[$sAttCode]) as $sColumn => $sValue) + { + $aUpdateValues[$sColumn] = $sValue; + } + } + + // Select... + // + // Skip, if a list of fields has been specified and it is not there + if (!array_key_exists($sAttCode, $aExpectedAtts)) continue; + + if ($oAttDef->IsExternalField()) + { + // skip, this will be handled in the joined tables + } + else + { + // standard field, or external key + // add it to the output + foreach ($oAttDef->GetSQLExpressions() as $sColId => $sSQLExpr) + { + foreach ($aExpectedAtts[$sAttCode] as $sAttAlias) + { + $aSelect[$sAttAlias.$sColId] = new FieldExpression($sSQLExpr, $sTableAlias); + } + } + } + } + + // 2 - WHERE + // + foreach(self::$m_aFilterDefs[$sTargetClass] as $sFltCode => $oFltAtt) + { + // Skip this filter if not defined in this table + if (self::$m_aFilterOrigins[$sTargetClass][$sFltCode] != $sTableClass) continue; + + // #@# todo - aller plus loin... a savoir que la table de translation doit contenir une "Expression" + foreach($oFltAtt->GetSQLExpressions() as $sColID => $sFltExpr) + { + // Note: I did not test it with filters relying on several expressions... + // as long as sColdID is empty, this is working, otherwise... ? + $aTranslation[$sTargetAlias][$sFltCode.$sColID] = array($sTableAlias, $sFltExpr); + } + } + + // #@# todo - See what a full text search condition should be + // 2' - WHERE / Full text search condition + // + if ($bIsOnQueriedClass) + { + $aFullText = $oFilter->GetCriteria_FullText(); + } + else + { + // Pourquoi ??? + $aFullText = array(); + } + + // 3 - The whole stuff, for this table only + // + $oSelectBase = new SQLQuery($sTable, $sTableAlias, $aSelect, null, $aFullText, $bIsOnQueriedClass, $aUpdateValues); + + // 4 - The external keys -> joins... + // + if (array_key_exists($sTableClass, $aExtKeys)) + { + foreach ($aExtKeys[$sTableClass] as $sKeyAttCode => $aExtFields) + { + $oKeyAttDef = self::GetAttributeDef($sTargetClass, $sKeyAttCode); + + $oExtFilter = $oFilter->GetCriteria_PointingTo($sKeyAttCode); + + // In case the join was not explicitely defined in the filter, + // we need to do it now + if (empty($oExtFilter)) + { + $sKeyClass = $oKeyAttDef->GetTargetClass(); + $sKeyClassAlias = self::GenerateUniqueAlias($aClassAliases, $sKeyClass.'_'.$sKeyAttCode, $sKeyClass); + $oExtFilter = new DBObjectSearch($sKeyClass, $sKeyClassAlias); + } + else + { + // The aliases should not conflict because normalization occured while building the filter + $sKeyClass = $oExtFilter->GetClass(); + $sKeyClassAlias = $oExtFilter->GetClassAlias(); + + // Note: there is no search condition in $oExtFilter, because normalization did merge the condition onto the top of the filter tree + } + + // Specify expected attributes for the target class query + // ... and use the current alias ! + $aExpAtts = array(); + $aIntermediateTranslation = array(); + foreach($aExtFields as $sAttCode => $oAtt) + { + + $sExtAttCode = $oAtt->GetExtAttCode(); + if (array_key_exists($sAttCode, $aExpectedAtts)) + { + // Request this attribute... transmit the alias ! + $aExpAtts[$sExtAttCode] = $aExpectedAtts[$sAttCode]; + } + // Translate mainclass.extfield => remoteclassalias.remotefieldcode + $oRemoteAttDef = self::GetAttributeDef($sKeyClass, $sExtAttCode); + foreach ($oRemoteAttDef->GetSQLExpressions() as $sColID => $sRemoteAttExpr) + { + $aIntermediateTranslation[$sTargetAlias.$sColID][$sAttCode] = array($sKeyClassAlias, $sRemoteAttExpr); + } + //#@# debug - echo "

$sTargetAlias.$sAttCode to $sKeyClassAlias.$sRemoteAttExpr (class: $sKeyClass)

\n"; + } + $oConditionTree = $oConditionTree->Translate($aIntermediateTranslation, false); + + self::DbgTrace("External key $sKeyAttCode (class: $sKeyClass), call MakeQuery()"); + $oSelectExtKey = self::MakeQuery($aSelectedClasses, $oConditionTree, $aClassAliases, $aTableAliases, $aTranslation, $oExtFilter, $aExpAtts); + + $sLocalKeyField = current($oKeyAttDef->GetSQLExpressions()); // get the first column for an external key + $sExternalKeyField = self::DBGetKey($sKeyClass); + self::DbgTrace("External key $sKeyAttCode, Join on $sLocalKeyField = $sExternalKeyField"); + if ($oKeyAttDef->IsNullAllowed()) + { + $oSelectBase->AddLeftJoin($oSelectExtKey, $sLocalKeyField, $sExternalKeyField); + } + else + { + $oSelectBase->AddInnerJoin($oSelectExtKey, $sLocalKeyField, $sExternalKeyField); + } + } + } + + //MyHelpers::var_dump_html($oSelectBase->RenderSelect()); + return $oSelectBase; + } + + public static function GenerateUniqueAlias(&$aAliases, $sNewName, $sRealName) + { + if (!array_key_exists($sNewName, $aAliases)) + { + $aAliases[$sNewName] = $sRealName; + return $sNewName; + } + + 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() + { + if (count(self::GetClasses()) == 0) + { + throw new CoreException("MetaModel::InitClasses() has not been called, or no class has been declared ?!?!"); + } + + $aErrors = array(); + $aSugFix = array(); + foreach (self::GetClasses() as $sClass) + { + if (self::IsAbstract($sClass)) continue; + + $sNameAttCode = self::GetNameAttributeCode($sClass); + if (empty($sNameAttCode)) + { + // let's try this !!! + // $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)) + { + $aErrors[$sClass][] = "Unkown attribute code '".$sNameAttCode."' for the name definition"; + $aSugFix[$sClass][] = "Expecting a value in ".implode(", ", self::GetAttributesList($sClass)); + } + + foreach(self::GetReconcKeys($sClass) as $sReconcKeyAttCode) + if (!empty($sReconcKeyAttCode) && !self::IsValidAttCode($sClass, $sReconcKeyAttCode)) + { + $aErrors[$sClass][] = "Unkown attribute code '".$sReconcKeyAttCode."' in the list of reconciliation keys"; + $aSugFix[$sClass][] = "Expecting a value in ".implode(", ", self::GetAttributesList($sClass)); + } + + foreach(self::ListAttributeDefs($sClass) as $sAttCode=>$oAttDef) + { + // It makes no sense to check the attributes again and again in the subclasses + if (self::$m_aAttribOrigins[$sClass][$sAttCode] != $sClass) continue; + + if ($oAttDef->IsExternalKey()) + { + if (!self::IsValidClass($oAttDef->GetTargetClass())) + { + $aErrors[$sClass][] = "Unkown class '".$oAttDef->GetTargetClass()."' for the external key '$sAttCode'"; + $aSugFix[$sClass][] = "Expecting a value in {".implode(", ", self::GetClasses())."}"; + } + } + elseif ($oAttDef->IsExternalField()) + { + $sKeyAttCode = $oAttDef->GetKeyAttCode(); + if (!self::IsValidAttCode($sClass, $sKeyAttCode) || !self::IsValidKeyAttCode($sClass, $sKeyAttCode)) + { + $aErrors[$sClass][] = "Unkown key attribute code '".$sKeyAttCode."' for the external field $sAttCode"; + $aSugFix[$sClass][] = "Expecting a value in {".implode(", ", self::GetKeysList($sClass))."}"; + } + else + { + $oKeyAttDef = self::GetAttributeDef($sClass, $sKeyAttCode); + $sTargetClass = $oKeyAttDef->GetTargetClass(); + $sExtAttCode = $oAttDef->GetExtAttCode(); + if (!self::IsValidAttCode($sTargetClass, $sExtAttCode)) + { + $aErrors[$sClass][] = "Unkown key attribute code '".$sExtAttCode."' for the external field $sAttCode"; + $aSugFix[$sClass][] = "Expecting a value in {".implode(", ", self::GetKeysList($sTargetClass))."}"; + } + } + } + else // standard attributes + { + // Check that the default values definition is a valid object! + $oValSetDef = $oAttDef->GetValuesDef(); + if (!is_null($oValSetDef) && !$oValSetDef instanceof ValueSetDefinition) + { + $aErrors[$sClass][] = "Allowed values for attribute $sAttCode is not of the relevant type"; + $aSugFix[$sClass][] = "Please set it as an instance of a ValueSetDefinition object."; + } + else + { + // Default value must be listed in the allowed values (if defined) + $aAllowedValues = self::GetAllowedValues_att($sClass, $sAttCode); + if (!is_null($aAllowedValues)) + { + $sDefaultValue = $oAttDef->GetDefaultValue(); + if (!array_key_exists($sDefaultValue, $aAllowedValues)) + { + $aErrors[$sClass][] = "Default value '".$sDefaultValue."' for attribute $sAttCode is not an allowed value"; + $aSugFix[$sClass][] = "Please pickup the default value out of {'".implode(", ", array_keys($aAllowedValues))."'}"; + } + } + } + } + // Check dependencies + if ($oAttDef->IsWritable()) + { + foreach ($oAttDef->GetPrerequisiteAttributes() as $sDependOnAttCode) + { + if (!self::IsValidAttCode($sClass, $sDependOnAttCode)) + { + $aErrors[$sClass][] = "Unkown attribute code '".$sDependOnAttCode."' in the list of prerequisite attributes"; + $aSugFix[$sClass][] = "Expecting a value in ".implode(", ", self::GetAttributesList($sClass)); + } + } + } + } + foreach(self::GetClassFilterDefs($sClass) as $sFltCode=>$oFilterDef) + { + if (method_exists($oFilterDef, '__GetRefAttribute')) + { + $oAttDef = $oFilterDef->__GetRefAttribute(); + if (!self::IsValidAttCode($sClass, $oAttDef->GetCode())) + { + $aErrors[$sClass][] = "Wrong attribute code '".$oAttDef->GetCode()."' (wrong class) for the \"basic\" filter $sFltCode"; + $aSugFix[$sClass][] = "Expecting a value in {".implode(", ", self::GetAttributesList($sClass))."}"; + } + } + } + + // Lifecycle + // + $sStateAttCode = self::GetStateAttributeCode($sClass); + if (strlen($sStateAttCode) > 0) + { + // Lifecycle - check that the state attribute does exist as an attribute + if (!self::IsValidAttCode($sClass, $sStateAttCode)) + { + $aErrors[$sClass][] = "Unkown attribute code '".$sStateAttCode."' for the state definition"; + $aSugFix[$sClass][] = "Expecting a value in {".implode(", ", self::GetAttributesList($sClass))."}"; + } + else + { + // Lifecycle - check that there is a value set constraint on the state attribute + $aAllowedValuesRaw = self::GetAllowedValues_att($sClass, $sStateAttCode); + $aStates = array_keys(self::EnumStates($sClass)); + if (is_null($aAllowedValuesRaw)) + { + $aErrors[$sClass][] = "Attribute '".$sStateAttCode."' will reflect the state of the object. It must be restricted to a set of values"; + $aSugFix[$sClass][] = "Please define its allowed_values property as [new ValueSetEnum('".implode(", ", $aStates)."')]"; + } + else + { + $aAllowedValues = array_keys($aAllowedValuesRaw); + + // Lifecycle - check the the state attribute allowed values are defined states + foreach($aAllowedValues as $sValue) + { + if (!in_array($sValue, $aStates)) + { + $aErrors[$sClass][] = "Attribute '".$sStateAttCode."' (object state) has an allowed value ($sValue) which is not a known state"; + $aSugFix[$sClass][] = "You may define its allowed_values property as [new ValueSetEnum('".implode(", ", $aStates)."')], or reconsider the list of states"; + } + } + + // Lifecycle - check that defined states are allowed values + foreach($aStates as $sStateValue) + { + if (!in_array($sStateValue, $aAllowedValues)) + { + $aErrors[$sClass][] = "Attribute '".$sStateAttCode."' (object state) has a state ($sStateValue) which is not an allowed value"; + $aSugFix[$sClass][] = "You may define its allowed_values property as [new ValueSetEnum('".implode(", ", $aStates)."')], or reconsider the list of states"; + } + } + } + + // Lifcycle - check that the action handlers are defined + foreach (self::EnumStates($sClass) as $sStateCode => $aStateDef) + { + foreach(self::EnumTransitions($sClass, $sStateCode) as $sStimulusCode => $aTransitionDef) + { + foreach ($aTransitionDef['actions'] as $sActionHandler) + { + if (!method_exists($sClass, $sActionHandler)) + { + $aErrors[$sClass][] = "Unknown function '$sActionHandler' in transition [$sStateCode/$sStimulusCode] for state attribute '$sStateAttCode'"; + $aSugFix[$sClass][] = "Specify a function which prototype is in the form [public function $sActionHandler(\$sStimulusCode){return true;}]"; + } + } + } + } + } + } + + // ZList + // + foreach(self::EnumZLists() as $sListCode) + { + foreach (self::GetZListItems($sClass, $sListCode) as $sMyAttCode) + { + if (!self::IsValidAttCode($sClass, $sMyAttCode)) + { + $aErrors[$sClass][] = "Unkown attribute code '".$sMyAttCode."' from ZList '$sListCode'"; + $aSugFix[$sClass][] = "Expecting a value in {".implode(", ", self::GetAttributesList($sClass))."}"; + } + } + } + } + if (count($aErrors) > 0) + { + echo "
"; + echo "

Business model inconsistencies have been found

\n"; + // #@# later -> this is the responsibility of the caller to format the output + foreach ($aErrors as $sClass => $aMessages) + { + echo "

Wrong declaration for class $sClass

\n"; + echo "
    \n"; + $i = 0; + foreach ($aMessages as $sMsg) + { + echo "
  • $sMsg ({$aSugFix[$sClass][$i]})
  • \n"; + $i++; + } + echo "
\n"; + } + echo "

Aborting...

\n"; + echo "
\n"; + exit; + } + } + + public static function DBShowApplyForm($sRepairUrl, $sSQLStatementArgName, $aSQLFixes) + { + if (empty($sRepairUrl)) return; + if (count($aSQLFixes) == 0) return; + + echo "
\n"; + echo " \n"; + echo " \n"; + echo "
\n"; + } + + public static function DBExists($bMustBeComplete = true) + { + // returns true if at least one table exists + // + + if (!CMDBSource::IsDB(self::$m_sDBName)) + { + return false; + } + CMDBSource::SelectDB(self::$m_sDBName); + + $aFound = array(); + $aMissing = array(); + foreach (self::DBEnumTables() as $sTable => $aClasses) + { + if (CMDBSource::IsTable($sTable)) + { + $aFound[] = $sTable; + } + else + { + $aMissing[] = $sTable; + } + } + + if (count($aFound) == 0) + { + // no expected table has been found + return false; + } + else + { + if (count($aMissing) == 0) + { + // the database is complete (still, could be some fields missing!) + return true; + } + else + { + // not all the tables, could be an older version + if ($bMustBeComplete) + { + return false; + } + else + { + return true; + } + } + } + } + + public static function DBDrop() + { + $bDropEntireDB = true; + + if (!empty(self::$m_sTablePrefix)) + { + // Do drop only tables corresponding to the sub-database (table prefix) + // then possibly drop the DB itself (if no table remain) + foreach (CMDBSource::EnumTables() as $sTable) + { + // perform a case insensitive test because on Windows the table names become lowercase :-( + if (strtolower(substr($sTable, 0, strlen(self::$m_sTablePrefix))) == strtolower(self::$m_sTablePrefix)) + { + CMDBSource::DropTable($sTable); + } + else + { + // There is at least one table which is out of the scope of the current application + $bDropEntireDB = false; + } + } + } + + if ($bDropEntireDB) + { + CMDBSource::DropDB(self::$m_sDBName); + } + } + + + public static function DBCreate() + { + // Note: we have to check if the DB does exist, because we may share the DB + // with other applications (in which case the DB does exist, not the tables with the given prefix) + if (!CMDBSource::IsDB(self::$m_sDBName)) + { + CMDBSource::CreateDB(self::$m_sDBName); + } + self::DBCreateTables(); + } + + protected static function DBCreateTables() + { + list($aErrors, $aSugFix) = self::DBCheckFormat(); + + $aSQL = array(); + foreach ($aSugFix as $sClass => $aTarget) + { + foreach ($aTarget as $aQueries) + { + foreach ($aQueries as $sQuery) + { + //$aSQL[] = $sQuery; + // forces a refresh of cached information + CMDBSource::CreateTable($sQuery); + } + } + } + // does not work -how to have multiple statements in a single query? + // $sDoCreateAll = implode(" ; ", $aSQL); + } + + public static function DBDump() + { + $aDataDump = array(); + foreach (self::DBEnumTables() as $sTable => $aClasses) + { + $aRows = CMDBSource::DumpTable($sTable); + $aDataDump[$sTable] = $aRows; + } + return $aDataDump; + } + + public static function DBCheckFormat() + { + $aErrors = array(); + $aSugFix = array(); + foreach (self::GetClasses() as $sClass) + { + if (self::IsAbstract($sClass)) continue; + + // Check that the table exists + // + $sTable = self::DBGetTable($sClass); + $sKeyField = self::DBGetKey($sClass); + $sAutoIncrement = (self::IsAutoIncrementKey($sClass) ? "AUTO_INCREMENT" : ""); + if (!CMDBSource::IsTable($sTable)) + { + $aErrors[$sClass]['*'][] = "table '$sTable' could not be found into the DB"; + $aSugFix[$sClass]['*'][] = "CREATE TABLE `$sTable` (`$sKeyField` INT(11) NOT NULL $sAutoIncrement PRIMARY KEY) ENGINE = innodb CHARACTER SET utf8 COLLATE utf8_unicode_ci"; + } + // Check that the key field exists + // + elseif (!CMDBSource::IsField($sTable, $sKeyField)) + { + $aErrors[$sClass]['id'][] = "key '$sKeyField' (table $sTable) could not be found"; + $aSugFix[$sClass]['id'][] = "ALTER TABLE `$sTable` ADD `$sKeyField` INT(11) NOT NULL $sAutoIncrement PRIMARY KEY"; + } + else + { + // Check the key field properties + // + if (!CMDBSource::IsKey($sTable, $sKeyField)) + { + $aErrors[$sClass]['id'][] = "key '$sKeyField' is not a key for table '$sTable'"; + $aSugFix[$sClass]['id'][] = "ALTER TABLE `$sTable`, DROP PRIMARY KEY, ADD PRIMARY key(`$sKeyField`)"; + } + if (self::IsAutoIncrementKey($sClass) && !CMDBSource::IsAutoIncrement($sTable, $sKeyField)) + { + $aErrors[$sClass]['id'][] = "key '$sKeyField' (table $sTable) is not automatically incremented"; + $aSugFix[$sClass]['id'][] = "ALTER TABLE `$sTable` CHANGE `$sKeyField` `$sKeyField` INT(11) NOT NULL AUTO_INCREMENT"; + } + } + + // Check that any defined field exists + // + $aTableInfo = CMDBSource::GetTableInfo($sTable); + + foreach(self::ListAttributeDefs($sClass) as $sAttCode=>$oAttDef) + { + // Skip this attribute if not originaly defined in this class + if (self::$m_aAttribOrigins[$sClass][$sAttCode] != $sClass) continue; + + foreach($oAttDef->GetSQLColumns() as $sField => $sDBFieldType) + { + $sFieldSpecs = $oAttDef->IsNullAllowed() ? "$sDBFieldType NULL" : "$sDBFieldType NOT NULL"; + if (!CMDBSource::IsField($sTable, $sField)) + { + $aErrors[$sClass][$sAttCode][] = "field '$sField' could not be found in table '$sTable'"; + $aSugFix[$sClass][$sAttCode][] = "ALTER TABLE `$sTable` ADD `$sField` $sFieldSpecs"; + if ($oAttDef->IsExternalKey()) + { + $aSugFix[$sClass][$sAttCode][] = "ALTER TABLE `$sTable` ADD INDEX (`$sField`)"; + } + } + else + { + // The field already exists, does it have the relevant properties? + // + $bToBeChanged = false; + if ($oAttDef->IsNullAllowed() != CMDBSource::IsNullAllowed($sTable, $sField)) + { + $bToBeChanged = true; + if ($oAttDef->IsNullAllowed()) + { + $aErrors[$sClass][$sAttCode][] = "field '$sField' in table '$sTable' could be NULL"; + } + else + { + $aErrors[$sClass][$sAttCode][] = "field '$sField' in table '$sTable' could NOT be NULL"; + } + } + $sActualFieldType = CMDBSource::GetFieldType($sTable, $sField); + if (strcasecmp($sDBFieldType, $sActualFieldType) != 0) + { + $bToBeChanged = true; + $aErrors[$sClass][$sAttCode][] = "field '$sField' in table '$sTable' has a wrong type: found '$sActualFieldType' while expecting '$sDBFieldType'"; + } + if ($bToBeChanged) + { + $aSugFix[$sClass][$sAttCode][] = "ALTER TABLE `$sTable` CHANGE `$sField` `$sField` $sFieldSpecs"; + } + + // Create indexes (external keys only... so far) + // + if ($oAttDef->IsExternalKey() && !CMDBSource::HasIndex($sTable, $sField)) + { + $aErrors[$sClass][$sAttCode][] = "Foreign key '$sField' in table '$sTable' should have an index"; + $aSugFix[$sClass][$sAttCode][] = "ALTER TABLE `$sTable` ADD INDEX (`$sField`)"; + } + } + } + } + } + return array($aErrors, $aSugFix); + } + + + private static function DBCheckIntegrity_Check2Delete($sSelWrongRecs, $sErrorDesc, $sClass, &$aErrorsAndFixes, &$iNewDelCount, &$aPlannedDel, $bProcessingFriends = false) + { + $sRootClass = self::GetRootClass($sClass); + $sTable = self::DBGetTable($sClass); + $sKeyField = self::DBGetKey($sClass); + + if (array_key_exists($sTable, $aPlannedDel) && count($aPlannedDel[$sTable]) > 0) + { + $sSelWrongRecs .= " AND maintable.`$sKeyField` NOT IN ('".implode("', '", $aPlannedDel[$sTable])."')"; + } + $aWrongRecords = CMDBSource::QueryToCol($sSelWrongRecs, "id"); + if (count($aWrongRecords) == 0) return; + + if (!array_key_exists($sRootClass, $aErrorsAndFixes)) $aErrorsAndFixes[$sRootClass] = array(); + if (!array_key_exists($sTable, $aErrorsAndFixes[$sRootClass])) $aErrorsAndFixes[$sRootClass][$sTable] = array(); + + foreach ($aWrongRecords as $iRecordId) + { + if (array_key_exists($iRecordId, $aErrorsAndFixes[$sRootClass][$sTable])) + { + switch ($aErrorsAndFixes[$sRootClass][$sTable][$iRecordId]['Action']) + { + case 'Delete': + // Already planned for a deletion + // Let's concatenate the errors description together + $aErrorsAndFixes[$sRootClass][$sTable][$iRecordId]['Reason'] .= ', '.$sErrorDesc; + break; + + case 'Update': + // Let's plan a deletion + break; + } + } + else + { + $aErrorsAndFixes[$sRootClass][$sTable][$iRecordId]['Reason'] = $sErrorDesc; + } + + if (!$bProcessingFriends) + { + if (!array_key_exists($sTable, $aPlannedDel) || !in_array($iRecordId, $aPlannedDel[$sTable])) + { + // Something new to be deleted... + $iNewDelCount++; + } + } + + $aErrorsAndFixes[$sRootClass][$sTable][$iRecordId]['Action'] = 'Delete'; + $aErrorsAndFixes[$sRootClass][$sTable][$iRecordId]['Action_Details'] = array(); + $aErrorsAndFixes[$sRootClass][$sTable][$iRecordId]['Pass'] = 123; + $aPlannedDel[$sTable][] = $iRecordId; + } + + // Now make sure that we would delete the records of the other tables for that class + // + if (!$bProcessingFriends) + { + $sDeleteKeys = "'".implode("', '", $aWrongRecords)."'"; + foreach (self::EnumChildClasses($sRootClass, ENUM_CHILD_CLASSES_ALL) as $sFriendClass) + { + $sFriendTable = self::DBGetTable($sFriendClass); + $sFriendKey = self::DBGetKey($sFriendClass); + + // skip the current table + if ($sFriendTable == $sTable) continue; + + $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 `$sTable`", $sFriendClass, $aErrorsAndFixes, $iNewDelCount, $aPlannedDel, true); + } + } + } + + private static function DBCheckIntegrity_Check2Update($sSelWrongRecs, $sErrorDesc, $sColumn, $sNewValue, $sClass, &$aErrorsAndFixes, &$iNewDelCount, &$aPlannedDel) + { + $sRootClass = self::GetRootClass($sClass); + $sTable = self::DBGetTable($sClass); + $sKeyField = self::DBGetKey($sClass); + + if (array_key_exists($sTable, $aPlannedDel) && count($aPlannedDel[$sTable]) > 0) + { + $sSelWrongRecs .= " AND maintable.`$sKeyField` NOT IN ('".implode("', '", $aPlannedDel[$sTable])."')"; + } + $aWrongRecords = CMDBSource::QueryToCol($sSelWrongRecs, "id"); + if (count($aWrongRecords) == 0) return; + + if (!array_key_exists($sRootClass, $aErrorsAndFixes)) $aErrorsAndFixes[$sRootClass] = array(); + if (!array_key_exists($sTable, $aErrorsAndFixes[$sRootClass])) $aErrorsAndFixes[$sRootClass][$sTable] = array(); + + foreach ($aWrongRecords as $iRecordId) + { + if (array_key_exists($iRecordId, $aErrorsAndFixes[$sRootClass][$sTable])) + { + switch ($aErrorsAndFixes[$sRootClass][$sTable][$iRecordId]['Action']) + { + case 'Delete': + // No need to update, the record will be deleted! + break; + + case 'Update': + // Already planned for an update + // Add this new update spec to the list + $bFoundSameSpec = false; + foreach ($aErrorsAndFixes[$sRootClass][$sTable][$iRecordId]['Action_Details'] as $aUpdateSpec) + { + if (($sColumn == $aUpdateSpec['column']) && ($sNewValue == $aUpdateSpec['newvalue'])) + { + $bFoundSameSpec = true; + } + } + if (!$bFoundSameSpec) + { + $aErrorsAndFixes[$sRootClass][$sTable][$iRecordId]['Action_Details'][] = (array('column' => $sColumn, 'newvalue'=>$sNewValue)); + $aErrorsAndFixes[$sRootClass][$sTable][$iRecordId]['Reason'] .= ', '.$sErrorDesc; + } + break; + } + } + else + { + $aErrorsAndFixes[$sRootClass][$sTable][$iRecordId]['Reason'] = $sErrorDesc; + $aErrorsAndFixes[$sRootClass][$sTable][$iRecordId]['Action'] = 'Update'; + $aErrorsAndFixes[$sRootClass][$sTable][$iRecordId]['Action_Details'] = array(array('column' => $sColumn, 'newvalue'=>$sNewValue)); + $aErrorsAndFixes[$sRootClass][$sTable][$iRecordId]['Pass'] = 123; + } + + } + } + + // returns the count of records found for deletion + public static function DBCheckIntegrity_SinglePass(&$aErrorsAndFixes, &$iNewDelCount, &$aPlannedDel) + { + foreach (self::GetClasses() as $sClass) + { + if (self::IsAbstract($sClass)) continue; + $sRootClass = self::GetRootClass($sClass); + $sTable = self::DBGetTable($sClass); + $sKeyField = self::DBGetKey($sClass); + + // Check that the final class field contains the name of a class which inherited from the current class + // + if (self::HasFinalClassField($sClass)) + { + $sFinalClassField = self::DBGetClassField($sClass); + + $aAllowedValues = self::EnumChildClasses($sClass, ENUM_CHILD_CLASSES_ALL); + $sAllowedValues = implode(",", CMDBSource::Quote($aAllowedValues, true)); + + $sSelWrongRecs = "SELECT DISTINCT maintable.`$sKeyField` AS id FROM `$sTable` AS maintable WHERE `$sFinalClassField` NOT IN ($sAllowedValues)"; + self::DBCheckIntegrity_Check2Delete($sSelWrongRecs, "final class (field `$sFinalClassField`) is wrong (expected a value in {".$sAllowedValues."})", $sClass, $aErrorsAndFixes, $iNewDelCount, $aPlannedDel); + } + + // Compound objects model - node/leaf classes (not the root itself) + // + if (!self::IsStandaloneClass($sClass) && !self::HasFinalClassField($sClass)) + { + $sRootTable = self::DBGetTable($sRootClass); + $sRootKey = self::DBGetKey($sRootClass); + $sFinalClassField = self::DBGetClassField($sRootClass); + + $aExpectedClasses = self::EnumChildClasses($sClass, ENUM_CHILD_CLASSES_ALL); + $sExpectedClasses = implode(",", CMDBSource::Quote($aExpectedClasses, true)); + + // 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 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 `$sTable`, but no counterpart in root table `$sRootTable` (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 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 `$sRootTable`, but no counterpart in table `$sTable` (root records pointing to a class in {".$sExpectedClasses."})", $sRootClass, $aErrorsAndFixes, $iNewDelCount, $aPlannedDel); + } + + foreach(self::ListAttributeDefs($sClass) as $sAttCode=>$oAttDef) + { + // Skip this attribute if not defined in this table + if (self::$m_aAttribOrigins[$sClass][$sAttCode] != $sClass) continue; + + if ($oAttDef->IsExternalKey()) + { + // Check that any external field is pointing to an existing object + // + $sRemoteClass = $oAttDef->GetTargetClass(); + $sRemoteTable = self::DBGetTable($sRemoteClass); + $sRemoteKey = self::DBGetKey($sRemoteClass); + + $sExtKeyField = current($oAttDef->GetSQLExpressions()); // get the first column for an external key + + // Note: a class/table may have an external key on itself + $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()) + { + // Exclude the records pointing to 0/null from the errors + $sSelWrongRecs .= " AND maintable.`$sExtKeyField` IS NOT NULL"; + $sSelWrongRecs .= " AND maintable.`$sExtKeyField` != 0"; + self::DBCheckIntegrity_Check2Update($sSelWrongRecs, "Record pointing to (external key '$sAttCode') non existing objects", $sExtKeyField, 'null', $sClass, $aErrorsAndFixes, $iNewDelCount, $aPlannedDel); + } + else + { + self::DBCheckIntegrity_Check2Delete($sSelWrongRecs, "Record pointing to (external key '$sAttCode') non existing objects", $sClass, $aErrorsAndFixes, $iNewDelCount, $aPlannedDel); + } + + // Do almost the same, taking into account the records planned for deletion + if (array_key_exists($sRemoteTable, $aPlannedDel) && count($aPlannedDel[$sRemoteTable]) > 0) + { + // This could be done by the mean of a 'OR ... IN (aIgnoreRecords) + // but in that case you won't be able to track the root cause (cascading) + $sSelWrongRecs = $sSelBase." WHERE maintable.`$sExtKeyField` IN ('".implode("', '", $aPlannedDel[$sRemoteTable])."')"; + if ($oAttDef->IsNullAllowed()) + { + // Exclude the records pointing to 0/null from the errors + $sSelWrongRecs .= " AND maintable.`$sExtKeyField` IS NOT NULL"; + $sSelWrongRecs .= " AND maintable.`$sExtKeyField` != 0"; + self::DBCheckIntegrity_Check2Update($sSelWrongRecs, "Record pointing to (external key '$sAttCode') a record planned for deletion", $sExtKeyField, 'null', $sClass, $aErrorsAndFixes, $iNewDelCount, $aPlannedDel); + } + else + { + self::DBCheckIntegrity_Check2Delete($sSelWrongRecs, "Record pointing to (external key '$sAttCode') a record planned for deletion", $sClass, $aErrorsAndFixes, $iNewDelCount, $aPlannedDel); + } + } + } + else if ($oAttDef->IsDirectField()) + { + // Check that the values fit the allowed values + // + $aAllowedValues = self::GetAllowedValues_att($sClass, $sAttCode); + if (!is_null($aAllowedValues) && count($aAllowedValues) > 0) + { + $sExpectedValues = implode(",", CMDBSource::Quote(array_keys($aAllowedValues), true)); + + $sMyAttributeField = current($oAttDef->GetSQLExpressions()); // get the first column for the moment + $sDefaultValue = $oAttDef->GetDefaultValue(); + $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 ('$sAttCode') with an unexpected value", $sMyAttributeField, CMDBSource::Quote($sDefaultValue), $sClass, $aErrorsAndFixes, $iNewDelCount, $aPlannedDel); + } + } + } + } + } + + public static function DBCheckIntegrity($sRepairUrl = "", $sSQLStatementArgName = "") + { + // Records in error, and action to be taken: delete or update + // by RootClass/Table/Record + $aErrorsAndFixes = array(); + + // Records to be ignored in the current/next pass + // by Table = array of RecordId + $aPlannedDel = array(); + + // Count of errors in the next pass: no error means that we can leave... + $iErrorCount = 0; + // Limit in case of a bug in the algorythm + $iLoopCount = 0; + + $iNewDelCount = 1; // startup... + while ($iNewDelCount > 0) + { + $iNewDelCount = 0; + self::DBCheckIntegrity_SinglePass($aErrorsAndFixes, $iNewDelCount, $aPlannedDel); + $iErrorCount += $iNewDelCount; + + // Safety net #1 - limit the planned deletions + // + $iMaxDel = 1000; + $iPlannedDel = 0; + foreach ($aPlannedDel as $sTable => $aPlannedDelOnTable) + { + $iPlannedDel += count($aPlannedDelOnTable); + } + if ($iPlannedDel > $iMaxDel) + { + throw new CoreWarning("DB Integrity Check safety net - Exceeding the limit of $iMaxDel planned record deletion"); + break; + } + // Safety net #2 - limit the iterations + // + $iLoopCount++; + $iMaxLoops = 10; + if ($iLoopCount > $iMaxLoops) + { + throw new CoreWarning("DB Integrity Check safety net - Reached the limit of $iMaxLoops loops"); + break; + } + } + + // Display the results + // + $iIssueCount = 0; + $aFixesDelete = array(); + $aFixesUpdate = array(); + + foreach ($aErrorsAndFixes as $sRootClass => $aTables) + { + foreach ($aTables as $sTable => $aRecords) + { + foreach ($aRecords as $iRecord => $aError) + { + $sAction = $aError['Action']; + $sReason = $aError['Reason']; + $iPass = $aError['Pass']; + + switch ($sAction) + { + case 'Delete': + $sActionDetails = ""; + $aFixesDelete[$sTable][] = $iRecord; + break; + + case 'Update': + $aUpdateDesc = array(); + foreach($aError['Action_Details'] as $aUpdateSpec) + { + $aUpdateDesc[] = $aUpdateSpec['column']." -> ".$aUpdateSpec['newvalue']; + $aFixesUpdate[$sTable][$aUpdateSpec['column']][$aUpdateSpec['newvalue']][] = $iRecord; + } + $sActionDetails = "Set ".implode(", ", $aUpdateDesc); + + break; + + default: + $sActionDetails = "bug: unknown action '$sAction'"; + } + $aIssues[] = "$sRootClass / $sTable / $iRecord / $sReason / $sAction / $sActionDetails"; + $iIssueCount++; + } + } + } + + if ($iIssueCount > 0) + { + // Build the queries to fix in the database + // + // First step, be able to get class data out of the table name + // Could be optimized, because we've made the job earlier... but few benefits, so... + $aTable2ClassProp = array(); + foreach (self::GetClasses() as $sClass) + { + if (self::IsAbstract($sClass)) continue; + + $sRootClass = self::GetRootClass($sClass); + $sTable = self::DBGetTable($sClass); + $sKeyField = self::DBGetKey($sClass); + + $aErrorsAndFixes[$sRootClass][$sTable] = array(); + $aTable2ClassProp[$sTable] = array('rootclass'=>$sRootClass, 'class'=>$sClass, 'keyfield'=>$sKeyField); + } + // Second step, build a flat list of SQL queries + $aSQLFixes = array(); + $iPlannedUpdate = 0; + foreach ($aFixesUpdate as $sTable => $aColumns) + { + foreach ($aColumns as $sColumn => $aNewValues) + { + foreach ($aNewValues as $sNewValue => $aRecords) + { + $iPlannedUpdate += count($aRecords); + $sWrongRecords = "'".implode("', '", $aRecords)."'"; + $sKeyField = $aTable2ClassProp[$sTable]['keyfield']; + + $aSQLFixes[] = "UPDATE `$sTable` SET `$sColumn` = $sNewValue WHERE `$sKeyField` IN ($sWrongRecords)"; + } + } + } + $iPlannedDel = 0; + foreach ($aFixesDelete as $sTable => $aRecords) + { + $iPlannedDel += count($aRecords); + $sWrongRecords = "'".implode("', '", $aRecords)."'"; + $sKeyField = $aTable2ClassProp[$sTable]['keyfield']; + + $aSQLFixes[] = "DELETE FROM `$sTable` WHERE `$sKeyField` IN ($sWrongRecords)"; + } + + // Report the results + // + echo "
"; + echo "

Database corruption error(s): $iErrorCount issues have been encountered. $iPlannedDel records will be deleted, $iPlannedUpdate records will be updated:

\n"; + // #@# later -> this is the responsibility of the caller to format the output + echo "
    \n"; + foreach ($aIssues as $sIssueDesc) + { + echo "
  • $sIssueDesc
  • \n"; + } + echo "
\n"; + self::DBShowApplyForm($sRepairUrl, $sSQLStatementArgName, $aSQLFixes); + echo "

Aborting...

\n"; + echo "
\n"; + exit; + } + } + + public static function Startup($sConfigFile, $bAllowMissingDB = false) + { + self::LoadConfig($sConfigFile); + if (self::DBExists()) + { + CMDBSource::SelectDB(self::$m_sDBName); + + // Some of the init could not be done earlier (requiring classes to be declared and DB to be accessible) + self::InitPlugins(); + } + else + { + if (!$bAllowMissingDB) + { + throw new CoreException('Database not found, check your configuration file', array('config_file'=>$sConfigFile, 'db_name'=>self::$m_sDBName)); + } + } + } + + public static function LoadConfig($sConfigFile) + { + $oConfig = new Config($sConfigFile); + + foreach ($oConfig->GetAppModules() as $sModule => $sToInclude) + { + self::Plugin($sConfigFile, 'application', $sToInclude); + } + foreach ($oConfig->GetDataModels() as $sModule => $sToInclude) + { + self::Plugin($sConfigFile, 'business', $sToInclude); + } + foreach ($oConfig->GetAddons() as $sModule => $sToInclude) + { + self::Plugin($sConfigFile, 'addons', $sToInclude); + } + + $sServer = $oConfig->GetDBHost(); + $sUser = $oConfig->GetDBUser(); + $sPwd = $oConfig->GetDBPwd(); + $sSource = $oConfig->GetDBName(); + $sTablePrefix = $oConfig->GetDBSubname(); + + // The include have been included, let's browse the existing classes and + // develop some data based on the proposed model + self::InitClasses($sTablePrefix); + + self::$m_sDBName = $sSource; + self::$m_sTablePrefix = $sTablePrefix; + + CMDBSource::Init($sServer, $sUser, $sPwd); // do not select the DB (could not exist) + } + + protected static $m_aPlugins = array(); + public static function RegisterPlugin($sType, $sName, $aInitCallSpec = array()) + { + self::$m_aPlugins[$sName] = array( + 'type' => $sType, + 'init' => $aInitCallSpec, + ); + } + + protected static function Plugin($sConfigFile, $sModuleType, $sToInclude) + { + if (!file_exists($sToInclude)) + { + throw new CoreException('Wrong filename in configuration file', array('file' => $sConfigFile, 'module' => $sModuleType, 'filename' => $sToInclude)); + } + require_once($sToInclude); + } + + protected static function InitPlugins() + { + foreach(self::$m_aPlugins as $sName => $aData) + { + $aCallSpec = @$aData['init']; + if (count($aCallSpec) == 2) + { + if (!is_callable($aCallSpec)) + { + throw new CoreException('Wrong declaration in plugin', array('plugin' => $aData['name'], 'type' => $aData['type'], 'class' => $aData['class'], 'init' => $aData['init'])); + } + call_user_func($aCallSpec); + } + } + } + + // Building an object + // + // + private static $aQueryCacheGetObject = array(); + private static $aQueryCacheGetObjectHits = array(); + public static function GetQueryCacheStatus() + { + $aRes = array(); + $iTotalHits = 0; + foreach(self::$aQueryCacheGetObjectHits as $sClass => $iHits) + { + $aRes[] = "$sClass: $iHits"; + $iTotalHits += $iHits; + } + return $iTotalHits.' ('.implode(', ', $aRes).')'; + } + + public static function MakeSingleRow($sClass, $iKey, $bMustBeFound = true) + { + if (!array_key_exists($sClass, self::$aQueryCacheGetObject)) + { + // NOTE: Quick and VERY dirty caching mechanism which relies on + // the fact that the string '987654321' will never appear in the + // standard query + // This will be replaced for sure with a prepared statement + // or a view... next optimization to come! + $oFilter = new DBObjectSearch($sClass); + $oFilter->AddCondition('id', 987654321, '='); + + $sSQL = self::MakeSelectQuery($oFilter); + self::$aQueryCacheGetObject[$sClass] = $sSQL; + self::$aQueryCacheGetObjectHits[$sClass] = 0; + } + else + { + $sSQL = self::$aQueryCacheGetObject[$sClass]; + self::$aQueryCacheGetObjectHits[$sClass] += 1; +// echo " -load $sClass/$iKey- ".self::$aQueryCacheGetObjectHits[$sClass]."
\n"; + } + $sSQL = str_replace('987654321', CMDBSource::Quote($iKey), $sSQL); + $res = CMDBSource::Query($sSQL); + + $aRow = CMDBSource::FetchArray($res); + CMDBSource::FreeResult($res); + if ($bMustBeFound && empty($aRow)) + { + throw new CoreException("No result for the single row query: '$sSQL'"); + } + return $aRow; + } + + public static function GetObjectByRow($sClass, $aRow, $sClassAlias = '') + { + self::_check_subclass($sClass); + + // Compound objects: if available, get the final object class + // + if (!array_key_exists("finalclass", $aRow)) + { + // Either this is a bug (forgot to specify a root class with a finalclass field + // Or this is the expected behavior, because the object is not made of several tables + } + elseif (empty($aRow["finalclass"])) + { + // The data is missing in the DB + // @#@ possible improvement: check that the class is valid ! + $sRootClass = self::GetRootClass($sClass); + $sFinalClassField = self::DBGetClassField($sRootClass); + throw new CoreException("Empty class name for object $sClass::{$aRow["id"]} (root class '$sRootClass', field '{$sFinalClassField}' is empty)"); + } + else + { + // do the job for the real target class + $sClass = $aRow["finalclass"]; + } + return new $sClass($aRow, $sClassAlias); + } + + public static function GetObject($sClass, $iKey, $bMustBeFound = true) + { + self::_check_subclass($sClass); + $aRow = self::MakeSingleRow($sClass, $iKey, $bMustBeFound); + if (empty($aRow)) + { + return null; + } + return self::GetObjectByRow($sClass, $aRow); + } + + public static function GetHyperLink($sTargetClass, $iKey) + { + if ($iKey < 0) + { + return "$sTargetClass: $iKey (invalid value)"; + } + $oObj = self::GetObject($sTargetClass, $iKey, false); + if (is_null($oObj)) + { + return "$sTargetClass: $iKey (not found)"; + } + return $oObj->GetHyperLink(); + } + + public static function NewObject($sClass) + { + self::_check_subclass($sClass); + return new $sClass(); + } + + public static function GetNextKey($sClass) + { + $sRootClass = MetaModel::GetRootClass($sClass); + $sRootTable = MetaModel::DBGetTable($sRootClass); + $iNextKey = CMDBSource::GetNextInsertId($sRootTable); + return $iNextKey; + } + + public static function BulkDelete(DBObjectSearch $oFilter) + { + $sSQL = self::MakeDeleteQuery($oFilter); + CMDBSource::Query($sSQL); + } + + public static function BulkUpdate(DBObjectSearch $oFilter, array $aValues) + { + // $aValues is an array of $sAttCode => $value + $sSQL = self::MakeUpdateQuery($oFilter, $aValues); + CMDBSource::Query($sSQL); + } + + // Links + // + // + public static function EnumReferencedClasses($sClass) + { + self::_check_subclass($sClass); + + // 1-N links (referenced by my class), returns an array of sAttCode=>sClass + $aResult = array(); + foreach(self::$m_aAttribDefs[$sClass] as $sAttCode=>$oAttDef) + { + if ($oAttDef->IsExternalKey()) + { + $aResult[$sAttCode] = $oAttDef->GetTargetClass(); + } + } + return $aResult; + } + public static function EnumReferencingClasses($sClass, $bSkipLinkingClasses = false, $bInnerJoinsOnly = false) + { + self::_check_subclass($sClass); + + if ($bSkipLinkingClasses) + { + $aLinksClasses = self::EnumLinksClasses(); + } + + // 1-N links (referencing my class), array of sClass => array of sAttcode + $aResult = array(); + foreach (self::$m_aAttribDefs as $sSomeClass=>$aClassAttributes) + { + if ($bSkipLinkingClasses && in_array($sSomeClass, $aLinksClasses)) continue; + + $aExtKeys = array(); + foreach ($aClassAttributes as $sAttCode=>$oAttDef) + { + if (self::$m_aAttribOrigins[$sSomeClass][$sAttCode] != $sSomeClass) continue; + if ($oAttDef->IsExternalKey() && (self::IsParentClass($oAttDef->GetTargetClass(), $sClass))) + { + if ($bInnerJoinsOnly && $oAttDef->IsNullAllowed()) continue; + // Ok, I want this one + $aExtKeys[$sAttCode] = $oAttDef; + } + } + if (count($aExtKeys) != 0) + { + $aResult[$sSomeClass] = $aExtKeys; + } + } + return $aResult; + } + public static function EnumLinksClasses() + { + // Returns a flat array of classes having at least two external keys + $aResult = array(); + foreach (self::$m_aAttribDefs as $sSomeClass=>$aClassAttributes) + { + $iExtKeyCount = 0; + foreach ($aClassAttributes as $sAttCode=>$oAttDef) + { + if (self::$m_aAttribOrigins[$sSomeClass][$sAttCode] != $sSomeClass) continue; + if ($oAttDef->IsExternalKey()) + { + $iExtKeyCount++; + } + } + if ($iExtKeyCount >= 2) + { + $aResult[] = $sSomeClass; + } + } + return $aResult; + } + public static function EnumLinkingClasses($sClass = "") + { + // N-N links, array of sLinkClass => (array of sAttCode=>sClass) + $aResult = array(); + foreach (self::EnumLinksClasses() as $sSomeClass) + { + $aTargets = array(); + $bFoundClass = false; + foreach (self::ListAttributeDefs($sSomeClass) as $sAttCode=>$oAttDef) + { + if (self::$m_aAttribOrigins[$sSomeClass][$sAttCode] != $sSomeClass) continue; + if ($oAttDef->IsExternalKey()) + { + $sRemoteClass = $oAttDef->GetTargetClass(); + if (empty($sClass)) + { + $aTargets[$sAttCode] = $sRemoteClass; + } + elseif ($sClass == $sRemoteClass) + { + $bFoundClass = true; + } + else + { + $aTargets[$sAttCode] = $sRemoteClass; + } + } + } + if (empty($sClass) || $bFoundClass) + { + $aResult[$sSomeClass] = $aTargets; + } + } + return $aResult; + } + + public static function GetLinkLabel($sLinkClass, $sAttCode) + { + self::_check_subclass($sLinkClass); + + // e.g. "supported by" (later: $this->GetLinkLabel(), computed on link data!) + return self::GetLabel($sLinkClass, $sAttCode); + } + + /** + * Replaces all the parameters by the values passed in the hash array + */ + static public function ApplyParams($aInput, $aParams) + { + $aSearches = array(); + $aReplacements = array(); + foreach($aParams as $sSearch => $sReplace) + { + $aSearches[] = '$'.$sSearch.'$'; + $aReplacements[] = $sReplace; + } + return str_replace($aSearches, $aReplacements, $aInput); + } + +} // class MetaModel + + +// Standard attribute lists +MetaModel::RegisterZList("noneditable", array("description"=>"non editable fields", "type"=>"attributes")); + +MetaModel::RegisterZList("details", array("description"=>"All attributes to be displayed for the 'details' of an object", "type"=>"attributes")); +MetaModel::RegisterZList("list", array("description"=>"All attributes to be displayed for a list of objects", "type"=>"attributes")); +MetaModel::RegisterZList("preview", array("description"=>"All attributes visible in preview mode", "type"=>"attributes")); + +MetaModel::RegisterZList("standard_search", array("description"=>"List of criteria for the standard search", "type"=>"filters")); +MetaModel::RegisterZList("advanced_search", array("description"=>"List of criteria for the advanced search", "type"=>"filters")); + + +?> diff --git a/core/oql/oql-parser.php b/core/oql/oql-parser.php index e8b52cc18..d97146a5d 100644 --- a/core/oql/oql-parser.php +++ b/core/oql/oql-parser.php @@ -113,62 +113,63 @@ class OQLParserRaw#line 102 "oql-parser.php" */ const SELECT = 1; const AS_ALIAS = 2; - const WHERE = 3; - const JOIN = 4; - const ON = 5; - const EQ = 6; - const PAR_OPEN = 7; - const PAR_CLOSE = 8; - const COMA = 9; - const INTERVAL = 10; - const F_SECOND = 11; - const F_MINUTE = 12; - const F_HOUR = 13; - const F_DAY = 14; - const F_MONTH = 15; - const F_YEAR = 16; - const DOT = 17; - const VARNAME = 18; - const NAME = 19; - const NUMVAL = 20; - const STRVAL = 21; - const NOT_EQ = 22; - const LOG_AND = 23; - const LOG_OR = 24; - const MATH_DIV = 25; - const MATH_MULT = 26; - const MATH_PLUS = 27; - const MATH_MINUS = 28; - const GT = 29; - const LT = 30; - const GE = 31; - const LE = 32; - const LIKE = 33; - const NOT_LIKE = 34; - const IN = 35; - const NOT_IN = 36; - const F_IF = 37; - const F_ELT = 38; - const F_COALESCE = 39; - const F_CONCAT = 40; - const F_SUBSTR = 41; - const F_TRIM = 42; - const F_DATE = 43; - const F_DATE_FORMAT = 44; - const F_CURRENT_DATE = 45; - const F_NOW = 46; - const F_TIME = 47; - const F_TO_DAYS = 48; - const F_FROM_DAYS = 49; - const F_DATE_ADD = 50; - const F_DATE_SUB = 51; - const F_ROUND = 52; - const F_FLOOR = 53; - const F_INET_ATON = 54; - const F_INET_NTOA = 55; - const YY_NO_ACTION = 219; - const YY_ACCEPT_ACTION = 218; - const YY_ERROR_ACTION = 217; + const FROM = 3; + const COMA = 4; + const WHERE = 5; + const JOIN = 6; + const ON = 7; + const EQ = 8; + const PAR_OPEN = 9; + const PAR_CLOSE = 10; + const INTERVAL = 11; + const F_SECOND = 12; + const F_MINUTE = 13; + const F_HOUR = 14; + const F_DAY = 15; + const F_MONTH = 16; + const F_YEAR = 17; + const DOT = 18; + const VARNAME = 19; + const NAME = 20; + const NUMVAL = 21; + const STRVAL = 22; + const NOT_EQ = 23; + const LOG_AND = 24; + const LOG_OR = 25; + const MATH_DIV = 26; + const MATH_MULT = 27; + const MATH_PLUS = 28; + const MATH_MINUS = 29; + const GT = 30; + const LT = 31; + const GE = 32; + const LE = 33; + const LIKE = 34; + const NOT_LIKE = 35; + const IN = 36; + const NOT_IN = 37; + const F_IF = 38; + const F_ELT = 39; + const F_COALESCE = 40; + const F_CONCAT = 41; + const F_SUBSTR = 42; + const F_TRIM = 43; + const F_DATE = 44; + const F_DATE_FORMAT = 45; + const F_CURRENT_DATE = 46; + const F_NOW = 47; + const F_TIME = 48; + const F_TO_DAYS = 49; + const F_FROM_DAYS = 50; + const F_DATE_ADD = 51; + const F_DATE_SUB = 52; + const F_ROUND = 53; + const F_FLOOR = 54; + const F_INET_ATON = 55; + const F_INET_NTOA = 56; + const YY_NO_ACTION = 234; + const YY_ACCEPT_ACTION = 233; + const YY_ERROR_ACTION = 232; /* Next are that tables used to determine what action to take based on the ** current state and lookahead token. These tables are used to implement @@ -220,171 +221,163 @@ class OQLParserRaw#line 102 "oql-parser.php" ** shifting non-terminals after a reduce. ** self::$yy_default Default action for each state. */ - const YY_SZ_ACTTAB = 432; + const YY_SZ_ACTTAB = 384; static public $yy_action = array( - /* 0 */ 4, 50, 81, 5, 97, 2, 70, 102, 103, 74, - /* 10 */ 10, 98, 82, 76, 80, 38, 90, 55, 79, 78, - /* 20 */ 77, 75, 57, 56, 58, 47, 48, 49, 46, 54, - /* 30 */ 99, 116, 115, 114, 113, 117, 118, 122, 121, 120, - /* 40 */ 119, 112, 111, 100, 101, 105, 106, 110, 109, 24, - /* 50 */ 6, 43, 43, 71, 86, 4, 92, 69, 29, 88, - /* 60 */ 93, 42, 102, 103, 74, 20, 98, 82, 76, 80, - /* 70 */ 79, 78, 77, 75, 68, 79, 78, 77, 75, 45, - /* 80 */ 45, 33, 64, 25, 25, 99, 116, 115, 114, 113, - /* 90 */ 117, 118, 122, 121, 120, 119, 112, 111, 100, 101, - /* 100 */ 105, 106, 110, 109, 4, 43, 8, 87, 11, 62, - /* 110 */ 41, 102, 103, 74, 9, 98, 82, 76, 80, 52, - /* 120 */ 51, 21, 85, 84, 12, 35, 7, 25, 108, 40, - /* 130 */ 18, 44, 3, 45, 99, 116, 115, 114, 113, 117, - /* 140 */ 118, 122, 121, 120, 119, 112, 111, 100, 101, 105, - /* 150 */ 106, 110, 109, 218, 104, 91, 43, 73, 73, 73, - /* 160 */ 96, 92, 37, 29, 88, 93, 42, 26, 23, 22, - /* 170 */ 19, 13, 15, 171, 31, 30, 95, 76, 80, 1, - /* 180 */ 79, 78, 77, 75, 45, 95, 72, 67, 66, 61, - /* 190 */ 60, 63, 107, 123, 6, 43, 73, 94, 16, 95, - /* 200 */ 92, 32, 29, 88, 93, 42, 39, 82, 22, 19, - /* 210 */ 36, 15, 187, 31, 83, 187, 187, 65, 187, 79, - /* 220 */ 78, 77, 75, 45, 43, 187, 187, 187, 187, 92, - /* 230 */ 32, 29, 88, 93, 42, 187, 187, 187, 19, 187, - /* 240 */ 15, 187, 31, 187, 187, 187, 59, 187, 79, 78, - /* 250 */ 77, 75, 45, 187, 187, 89, 43, 187, 187, 187, - /* 260 */ 187, 92, 37, 29, 88, 93, 42, 187, 187, 187, - /* 270 */ 19, 187, 15, 187, 31, 187, 187, 187, 187, 187, - /* 280 */ 79, 78, 77, 75, 45, 43, 187, 187, 187, 187, - /* 290 */ 92, 17, 29, 88, 93, 42, 187, 187, 187, 19, - /* 300 */ 187, 15, 187, 31, 187, 187, 187, 187, 187, 79, - /* 310 */ 78, 77, 75, 45, 187, 187, 43, 187, 187, 187, - /* 320 */ 187, 92, 28, 29, 88, 93, 42, 187, 187, 187, - /* 330 */ 19, 187, 15, 187, 31, 187, 187, 187, 187, 187, - /* 340 */ 79, 78, 77, 75, 45, 43, 187, 187, 187, 187, - /* 350 */ 92, 187, 29, 88, 93, 42, 187, 187, 187, 19, - /* 360 */ 187, 15, 187, 34, 187, 187, 187, 187, 187, 79, - /* 370 */ 78, 77, 75, 45, 43, 187, 187, 187, 187, 92, - /* 380 */ 187, 29, 88, 93, 42, 187, 43, 187, 19, 187, - /* 390 */ 14, 92, 187, 27, 88, 93, 42, 187, 79, 78, - /* 400 */ 77, 75, 45, 43, 187, 187, 187, 53, 41, 187, - /* 410 */ 79, 78, 77, 75, 45, 187, 187, 187, 187, 187, - /* 420 */ 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, - /* 430 */ 187, 45, + /* 0 */ 4, 117, 5, 11, 8, 106, 121, 122, 130, 103, + /* 10 */ 89, 91, 82, 83, 26, 3, 134, 118, 116, 12, + /* 20 */ 105, 70, 54, 58, 60, 59, 63, 64, 57, 90, + /* 30 */ 107, 108, 127, 126, 125, 123, 124, 128, 129, 133, + /* 40 */ 132, 131, 113, 112, 81, 109, 110, 114, 16, 52, + /* 50 */ 69, 31, 30, 29, 95, 97, 4, 33, 96, 101, + /* 60 */ 49, 27, 121, 122, 130, 25, 89, 91, 82, 83, + /* 70 */ 94, 86, 85, 84, 94, 86, 85, 84, 50, 28, + /* 80 */ 141, 141, 74, 25, 53, 90, 107, 108, 127, 126, + /* 90 */ 125, 123, 124, 128, 129, 133, 132, 131, 113, 112, + /* 100 */ 81, 109, 110, 114, 4, 87, 42, 88, 93, 23, + /* 110 */ 121, 122, 130, 74, 89, 91, 82, 83, 46, 2, + /* 120 */ 7, 94, 86, 85, 84, 102, 82, 83, 19, 48, + /* 130 */ 62, 45, 105, 90, 107, 108, 127, 126, 125, 123, + /* 140 */ 124, 128, 129, 133, 132, 131, 113, 112, 81, 109, + /* 150 */ 110, 114, 233, 111, 100, 52, 56, 74, 74, 74, + /* 160 */ 6, 97, 37, 34, 96, 101, 49, 17, 38, 186, + /* 170 */ 22, 23, 14, 6, 41, 44, 76, 55, 23, 52, + /* 180 */ 94, 86, 85, 84, 50, 97, 40, 34, 96, 101, + /* 190 */ 49, 47, 20, 75, 22, 52, 14, 1, 41, 35, + /* 200 */ 61, 51, 67, 52, 94, 86, 85, 84, 50, 97, + /* 210 */ 40, 34, 96, 101, 49, 13, 104, 52, 22, 24, + /* 220 */ 14, 74, 41, 66, 50, 10, 80, 91, 94, 86, + /* 230 */ 85, 84, 50, 98, 52, 25, 36, 120, 119, 23, + /* 240 */ 97, 37, 34, 96, 101, 49, 50, 92, 74, 22, + /* 250 */ 52, 14, 43, 41, 71, 68, 51, 23, 52, 94, + /* 260 */ 86, 85, 84, 50, 97, 18, 34, 96, 101, 49, + /* 270 */ 193, 193, 99, 22, 193, 14, 193, 41, 193, 50, + /* 280 */ 9, 193, 52, 94, 86, 85, 84, 50, 97, 32, + /* 290 */ 34, 96, 101, 49, 115, 193, 193, 22, 193, 14, + /* 300 */ 193, 41, 193, 193, 193, 193, 52, 94, 86, 85, + /* 310 */ 84, 50, 97, 193, 34, 96, 101, 49, 193, 193, + /* 320 */ 193, 22, 193, 14, 193, 39, 193, 193, 193, 193, + /* 330 */ 52, 94, 86, 85, 84, 50, 97, 193, 34, 96, + /* 340 */ 101, 49, 193, 193, 193, 22, 193, 15, 65, 77, + /* 350 */ 79, 78, 73, 72, 52, 94, 86, 85, 84, 50, + /* 360 */ 97, 105, 34, 96, 101, 49, 193, 193, 193, 21, + /* 370 */ 193, 193, 193, 193, 193, 193, 193, 193, 193, 94, + /* 380 */ 86, 85, 84, 50, ); static public $yy_lookahead = array( - /* 0 */ 7, 6, 68, 10, 8, 9, 62, 14, 15, 16, - /* 10 */ 7, 18, 19, 20, 21, 81, 62, 22, 84, 85, - /* 20 */ 86, 87, 27, 28, 29, 30, 31, 32, 33, 34, - /* 30 */ 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, - /* 40 */ 47, 48, 49, 50, 51, 52, 53, 54, 55, 1, - /* 50 */ 80, 60, 60, 83, 68, 7, 65, 65, 67, 68, - /* 60 */ 69, 70, 14, 15, 16, 74, 18, 19, 20, 21, - /* 70 */ 84, 85, 86, 87, 23, 84, 85, 86, 87, 88, - /* 80 */ 88, 61, 61, 63, 63, 37, 38, 39, 40, 41, - /* 90 */ 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, - /* 100 */ 52, 53, 54, 55, 7, 60, 77, 8, 9, 64, - /* 110 */ 65, 14, 15, 16, 75, 18, 19, 20, 21, 90, - /* 120 */ 91, 2, 35, 36, 5, 61, 79, 63, 89, 60, - /* 130 */ 60, 60, 3, 88, 37, 38, 39, 40, 41, 42, - /* 140 */ 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, - /* 150 */ 53, 54, 55, 57, 58, 59, 60, 88, 88, 88, - /* 160 */ 8, 65, 66, 67, 68, 69, 70, 2, 60, 4, - /* 170 */ 74, 5, 76, 17, 78, 17, 24, 20, 21, 7, - /* 180 */ 84, 85, 86, 87, 88, 24, 11, 12, 13, 14, - /* 190 */ 15, 16, 25, 26, 80, 60, 88, 73, 6, 24, - /* 200 */ 65, 66, 67, 68, 69, 70, 71, 19, 4, 74, - /* 210 */ 72, 76, 92, 78, 88, 92, 92, 82, 92, 84, - /* 220 */ 85, 86, 87, 88, 60, 92, 92, 92, 92, 65, - /* 230 */ 66, 67, 68, 69, 70, 92, 92, 92, 74, 92, - /* 240 */ 76, 92, 78, 92, 92, 92, 82, 92, 84, 85, - /* 250 */ 86, 87, 88, 92, 92, 59, 60, 92, 92, 92, - /* 260 */ 92, 65, 66, 67, 68, 69, 70, 92, 92, 92, - /* 270 */ 74, 92, 76, 92, 78, 92, 92, 92, 92, 92, - /* 280 */ 84, 85, 86, 87, 88, 60, 92, 92, 92, 92, - /* 290 */ 65, 66, 67, 68, 69, 70, 92, 92, 92, 74, - /* 300 */ 92, 76, 92, 78, 92, 92, 92, 92, 92, 84, - /* 310 */ 85, 86, 87, 88, 92, 92, 60, 92, 92, 92, - /* 320 */ 92, 65, 66, 67, 68, 69, 70, 92, 92, 92, - /* 330 */ 74, 92, 76, 92, 78, 92, 92, 92, 92, 92, - /* 340 */ 84, 85, 86, 87, 88, 60, 92, 92, 92, 92, - /* 350 */ 65, 92, 67, 68, 69, 70, 92, 92, 92, 74, - /* 360 */ 92, 76, 92, 78, 92, 92, 92, 92, 92, 84, - /* 370 */ 85, 86, 87, 88, 60, 92, 92, 92, 92, 65, - /* 380 */ 92, 67, 68, 69, 70, 92, 60, 92, 74, 92, - /* 390 */ 76, 65, 92, 67, 68, 69, 70, 92, 84, 85, - /* 400 */ 86, 87, 88, 60, 92, 92, 92, 64, 65, 92, - /* 410 */ 84, 85, 86, 87, 88, 92, 92, 92, 92, 92, - /* 420 */ 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, - /* 430 */ 92, 88, + /* 0 */ 9, 8, 11, 4, 79, 10, 15, 16, 17, 10, + /* 10 */ 19, 20, 21, 22, 2, 5, 23, 92, 93, 7, + /* 20 */ 25, 28, 29, 30, 31, 32, 33, 34, 35, 38, + /* 30 */ 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, + /* 40 */ 49, 50, 51, 52, 53, 54, 55, 56, 1, 61, + /* 50 */ 63, 3, 4, 61, 70, 67, 9, 69, 70, 71, + /* 60 */ 72, 2, 15, 16, 17, 6, 19, 20, 21, 22, + /* 70 */ 86, 87, 88, 89, 86, 87, 88, 89, 90, 2, + /* 80 */ 3, 4, 90, 6, 61, 38, 39, 40, 41, 42, + /* 90 */ 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, + /* 100 */ 53, 54, 55, 56, 9, 70, 62, 36, 37, 65, + /* 110 */ 15, 16, 17, 90, 19, 20, 21, 22, 83, 4, + /* 120 */ 81, 86, 87, 88, 89, 10, 21, 22, 61, 61, + /* 130 */ 61, 64, 25, 38, 39, 40, 41, 42, 43, 44, + /* 140 */ 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, + /* 150 */ 55, 56, 58, 59, 60, 61, 24, 90, 90, 90, + /* 160 */ 82, 67, 68, 69, 70, 71, 72, 8, 62, 18, + /* 170 */ 76, 65, 78, 82, 80, 62, 85, 63, 65, 61, + /* 180 */ 86, 87, 88, 89, 90, 67, 68, 69, 70, 71, + /* 190 */ 72, 73, 61, 63, 76, 61, 78, 9, 80, 18, + /* 200 */ 66, 67, 84, 61, 86, 87, 88, 89, 90, 67, + /* 210 */ 68, 69, 70, 71, 72, 7, 75, 61, 76, 61, + /* 220 */ 78, 90, 80, 67, 90, 9, 84, 20, 86, 87, + /* 230 */ 88, 89, 90, 60, 61, 6, 62, 26, 27, 65, + /* 240 */ 67, 68, 69, 70, 71, 72, 90, 90, 90, 76, + /* 250 */ 61, 78, 74, 80, 62, 66, 67, 65, 61, 86, + /* 260 */ 87, 88, 89, 90, 67, 68, 69, 70, 71, 72, + /* 270 */ 94, 94, 63, 76, 94, 78, 94, 80, 94, 90, + /* 280 */ 77, 94, 61, 86, 87, 88, 89, 90, 67, 68, + /* 290 */ 69, 70, 71, 72, 91, 94, 94, 76, 94, 78, + /* 300 */ 94, 80, 94, 94, 94, 94, 61, 86, 87, 88, + /* 310 */ 89, 90, 67, 94, 69, 70, 71, 72, 94, 94, + /* 320 */ 94, 76, 94, 78, 94, 80, 94, 94, 94, 94, + /* 330 */ 61, 86, 87, 88, 89, 90, 67, 94, 69, 70, + /* 340 */ 71, 72, 94, 94, 94, 76, 94, 78, 12, 13, + /* 350 */ 14, 15, 16, 17, 61, 86, 87, 88, 89, 90, + /* 360 */ 67, 25, 69, 70, 71, 72, 94, 94, 94, 76, + /* 370 */ 94, 94, 94, 94, 94, 94, 94, 94, 94, 86, + /* 380 */ 87, 88, 89, 90, ); - const YY_SHIFT_USE_DFLT = -8; - const YY_SHIFT_MAX = 45; + const YY_SHIFT_USE_DFLT = -10; + const YY_SHIFT_MAX = 53; static public $yy_shift_ofst = array( - /* 0 */ 48, -7, -7, 97, 97, 97, 97, 97, 97, 97, - /* 10 */ 157, 157, 188, 188, -5, -5, 188, 175, 165, 167, - /* 20 */ 167, 188, 188, 204, 188, 204, 188, 87, 152, 87, - /* 30 */ 188, 51, 161, 129, 51, 129, 3, 161, 99, -4, - /* 40 */ 119, 192, 172, 158, 166, 156, + /* 0 */ 47, -9, -9, 95, 95, 95, 95, 95, 95, 95, + /* 10 */ 105, 105, 207, 207, -7, -7, 207, 207, 336, 77, + /* 20 */ 59, 211, 211, 229, 229, 207, 207, 207, 207, 229, + /* 30 */ 207, 207, -5, 71, 71, 207, 10, 107, 10, 132, + /* 40 */ 107, 132, 10, 216, 10, 48, -1, 115, 12, 188, + /* 50 */ 151, 159, 181, 208, ); - const YY_REDUCE_USE_DFLT = -67; - const YY_REDUCE_MAX = 37; + const YY_REDUCE_USE_DFLT = -76; + const YY_REDUCE_MAX = 44; static public $yy_reduce_ofst = array( - /* 0 */ 96, 135, 164, 196, 256, 225, 285, 314, -9, 326, - /* 10 */ -66, -14, 343, 45, 29, 29, -8, -30, 64, 39, - /* 20 */ 39, 71, 69, 20, 70, 21, 108, 138, 114, 138, - /* 30 */ 126, 47, 114, -56, 47, -46, 124, 114, + /* 0 */ 94, 118, 142, 173, 221, 197, 245, 269, 293, -12, + /* 10 */ 35, -16, 134, 189, -75, -75, 67, 156, 91, 174, + /* 20 */ 113, 203, 203, 192, 44, 68, 23, -8, 158, 106, + /* 30 */ 69, 131, 78, 178, 178, 157, 209, 78, 114, 39, + /* 40 */ 78, 39, -13, 141, 130, ); static public $yyExpectedTokens = array( - /* 0 */ array(1, 7, 14, 15, 16, 18, 19, 20, 21, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, ), - /* 1 */ array(7, 10, 14, 15, 16, 18, 19, 20, 21, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, ), - /* 2 */ array(7, 10, 14, 15, 16, 18, 19, 20, 21, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, ), - /* 3 */ array(7, 14, 15, 16, 18, 19, 20, 21, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, ), - /* 4 */ array(7, 14, 15, 16, 18, 19, 20, 21, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, ), - /* 5 */ array(7, 14, 15, 16, 18, 19, 20, 21, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, ), - /* 6 */ array(7, 14, 15, 16, 18, 19, 20, 21, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, ), - /* 7 */ array(7, 14, 15, 16, 18, 19, 20, 21, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, ), - /* 8 */ array(7, 14, 15, 16, 18, 19, 20, 21, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, ), - /* 9 */ array(7, 14, 15, 16, 18, 19, 20, 21, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, ), - /* 10 */ array(20, 21, ), - /* 11 */ array(20, 21, ), - /* 12 */ array(19, ), - /* 13 */ array(19, ), - /* 14 */ array(6, 22, 27, 28, 29, 30, 31, 32, 33, 34, ), - /* 15 */ array(6, 22, 27, 28, 29, 30, 31, 32, 33, 34, ), - /* 16 */ array(19, ), - /* 17 */ array(11, 12, 13, 14, 15, 16, 24, ), - /* 18 */ array(2, 4, ), - /* 19 */ array(25, 26, ), - /* 20 */ array(25, 26, ), - /* 21 */ array(19, ), - /* 22 */ array(19, ), - /* 23 */ array(4, ), - /* 24 */ array(19, ), - /* 25 */ array(4, ), - /* 26 */ array(19, ), - /* 27 */ array(35, 36, ), - /* 28 */ array(8, 24, ), - /* 29 */ array(35, 36, ), - /* 30 */ array(19, ), - /* 31 */ array(23, ), - /* 32 */ array(24, ), - /* 33 */ array(3, ), - /* 34 */ array(23, ), - /* 35 */ array(3, ), - /* 36 */ array(7, ), - /* 37 */ array(24, ), - /* 38 */ array(8, 9, ), - /* 39 */ array(8, 9, ), - /* 40 */ array(2, 5, ), - /* 41 */ array(6, ), - /* 42 */ array(7, ), - /* 43 */ array(17, ), + /* 0 */ array(1, 9, 15, 16, 17, 19, 20, 21, 22, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, ), + /* 1 */ array(9, 11, 15, 16, 17, 19, 20, 21, 22, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, ), + /* 2 */ array(9, 11, 15, 16, 17, 19, 20, 21, 22, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, ), + /* 3 */ array(9, 15, 16, 17, 19, 20, 21, 22, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, ), + /* 4 */ array(9, 15, 16, 17, 19, 20, 21, 22, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, ), + /* 5 */ array(9, 15, 16, 17, 19, 20, 21, 22, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, ), + /* 6 */ array(9, 15, 16, 17, 19, 20, 21, 22, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, ), + /* 7 */ array(9, 15, 16, 17, 19, 20, 21, 22, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, ), + /* 8 */ array(9, 15, 16, 17, 19, 20, 21, 22, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, ), + /* 9 */ array(9, 15, 16, 17, 19, 20, 21, 22, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, ), + /* 10 */ array(21, 22, ), + /* 11 */ array(21, 22, ), + /* 12 */ array(20, ), + /* 13 */ array(20, ), + /* 14 */ array(8, 23, 28, 29, 30, 31, 32, 33, 34, 35, ), + /* 15 */ array(8, 23, 28, 29, 30, 31, 32, 33, 34, 35, ), + /* 16 */ array(20, ), + /* 17 */ array(20, ), + /* 18 */ array(12, 13, 14, 15, 16, 17, 25, ), + /* 19 */ array(2, 3, 4, 6, ), + /* 20 */ array(2, 6, ), + /* 21 */ array(26, 27, ), + /* 22 */ array(26, 27, ), + /* 23 */ array(6, ), + /* 24 */ array(6, ), + /* 25 */ array(20, ), + /* 26 */ array(20, ), + /* 27 */ array(20, ), + /* 28 */ array(20, ), + /* 29 */ array(6, ), + /* 30 */ array(20, ), + /* 31 */ array(20, ), + /* 32 */ array(10, 25, ), + /* 33 */ array(36, 37, ), + /* 34 */ array(36, 37, ), + /* 35 */ array(20, ), + /* 36 */ array(5, ), + /* 37 */ array(25, ), + /* 38 */ array(5, ), + /* 39 */ array(24, ), + /* 40 */ array(25, ), + /* 41 */ array(24, ), + /* 42 */ array(5, ), + /* 43 */ array(9, ), /* 44 */ array(5, ), - /* 45 */ array(17, ), - /* 46 */ array(), - /* 47 */ array(), - /* 48 */ array(), - /* 49 */ array(), - /* 50 */ array(), - /* 51 */ array(), - /* 52 */ array(), - /* 53 */ array(), + /* 45 */ array(3, 4, ), + /* 46 */ array(4, 10, ), + /* 47 */ array(4, 10, ), + /* 48 */ array(2, 7, ), + /* 49 */ array(9, ), + /* 50 */ array(18, ), + /* 51 */ array(8, ), + /* 52 */ array(18, ), + /* 53 */ array(7, ), /* 54 */ array(), /* 55 */ array(), /* 56 */ array(), @@ -455,21 +448,33 @@ static public $yy_action = array( /* 121 */ array(), /* 122 */ array(), /* 123 */ array(), + /* 124 */ array(), + /* 125 */ array(), + /* 126 */ array(), + /* 127 */ array(), + /* 128 */ array(), + /* 129 */ array(), + /* 130 */ array(), + /* 131 */ array(), + /* 132 */ array(), + /* 133 */ array(), + /* 134 */ array(), ); static public $yy_default = array( - /* 0 */ 217, 154, 217, 217, 217, 217, 217, 217, 217, 217, - /* 10 */ 217, 217, 217, 217, 148, 147, 217, 217, 132, 145, - /* 20 */ 146, 217, 217, 132, 217, 131, 217, 144, 217, 143, - /* 30 */ 217, 149, 157, 129, 150, 129, 217, 136, 217, 217, - /* 40 */ 217, 217, 217, 217, 217, 169, 191, 188, 189, 190, - /* 50 */ 179, 178, 177, 134, 192, 180, 186, 185, 187, 156, - /* 60 */ 163, 162, 133, 164, 130, 155, 161, 160, 181, 135, - /* 70 */ 127, 158, 159, 171, 208, 168, 174, 167, 166, 165, - /* 80 */ 175, 152, 173, 170, 194, 193, 153, 151, 137, 128, - /* 90 */ 126, 125, 138, 139, 142, 182, 141, 140, 172, 195, - /* 100 */ 211, 212, 210, 209, 124, 213, 214, 183, 176, 216, - /* 110 */ 215, 207, 206, 199, 198, 197, 196, 200, 201, 205, - /* 120 */ 204, 203, 202, 184, + /* 0 */ 232, 169, 232, 232, 232, 232, 232, 232, 232, 232, + /* 10 */ 232, 232, 232, 232, 162, 163, 232, 232, 232, 147, + /* 20 */ 147, 161, 160, 146, 147, 232, 232, 232, 232, 147, + /* 30 */ 232, 232, 232, 159, 158, 232, 144, 151, 144, 165, + /* 40 */ 172, 164, 144, 232, 144, 232, 232, 232, 232, 232, + /* 50 */ 184, 232, 232, 232, 201, 140, 196, 207, 202, 204, + /* 60 */ 203, 149, 142, 205, 206, 174, 150, 170, 148, 138, + /* 70 */ 200, 145, 179, 178, 186, 139, 173, 175, 177, 176, + /* 80 */ 171, 228, 189, 190, 183, 182, 181, 167, 208, 187, + /* 90 */ 210, 188, 185, 209, 180, 168, 152, 153, 143, 137, + /* 100 */ 136, 154, 155, 166, 157, 197, 156, 211, 212, 229, + /* 110 */ 230, 135, 227, 226, 231, 191, 193, 194, 192, 199, + /* 120 */ 198, 225, 224, 216, 217, 215, 214, 213, 218, 219, + /* 130 */ 223, 222, 221, 220, 195, ); /* The next thing included is series of defines which control ** various aspects of the generated parser. @@ -486,11 +491,11 @@ static public $yy_action = array( ** self::YYERRORSYMBOL is the code number of the error symbol. If not ** defined, then do no error processing. */ - const YYNOCODE = 93; + const YYNOCODE = 95; const YYSTACKDEPTH = 100; - const YYNSTATE = 124; - const YYNRULE = 93; - const YYERRORSYMBOL = 56; + const YYNSTATE = 135; + const YYNRULE = 97; + const YYERRORSYMBOL = 57; const YYERRSYMDT = 'yy0'; const YYFALLBACK = 0; /** The next table maps tokens into fallback tokens. If a construct @@ -572,29 +577,30 @@ static public $yy_action = array( * @var array */ static public $yyTokenName = array( - '$', 'SELECT', 'AS_ALIAS', 'WHERE', - 'JOIN', 'ON', 'EQ', 'PAR_OPEN', - 'PAR_CLOSE', 'COMA', 'INTERVAL', 'F_SECOND', - 'F_MINUTE', 'F_HOUR', 'F_DAY', 'F_MONTH', - 'F_YEAR', 'DOT', 'VARNAME', 'NAME', - 'NUMVAL', 'STRVAL', 'NOT_EQ', 'LOG_AND', - 'LOG_OR', 'MATH_DIV', 'MATH_MULT', 'MATH_PLUS', - 'MATH_MINUS', 'GT', 'LT', 'GE', - 'LE', 'LIKE', 'NOT_LIKE', 'IN', - 'NOT_IN', 'F_IF', 'F_ELT', 'F_COALESCE', - 'F_CONCAT', 'F_SUBSTR', 'F_TRIM', 'F_DATE', - 'F_DATE_FORMAT', 'F_CURRENT_DATE', 'F_NOW', 'F_TIME', - 'F_TO_DAYS', 'F_FROM_DAYS', 'F_DATE_ADD', 'F_DATE_SUB', - 'F_ROUND', 'F_FLOOR', 'F_INET_ATON', 'F_INET_NTOA', - 'error', 'result', 'query', 'condition', - 'class_name', 'join_statement', 'where_statement', 'join_item', - 'join_condition', 'field_id', 'expression_prio4', 'expression_basic', - 'scalar', 'var_name', 'func_name', 'arg_list', - 'list_operator', 'list', 'expression_prio1', 'operator1', - 'expression_prio2', 'operator2', 'expression_prio3', 'operator3', - 'operator4', 'scalar_list', 'argument', 'interval_unit', - 'num_scalar', 'str_scalar', 'num_value', 'str_value', - 'name', 'num_operator1', 'num_operator2', 'str_operator', + '$', 'SELECT', 'AS_ALIAS', 'FROM', + 'COMA', 'WHERE', 'JOIN', 'ON', + 'EQ', 'PAR_OPEN', 'PAR_CLOSE', 'INTERVAL', + 'F_SECOND', 'F_MINUTE', 'F_HOUR', 'F_DAY', + 'F_MONTH', 'F_YEAR', 'DOT', 'VARNAME', + 'NAME', 'NUMVAL', 'STRVAL', 'NOT_EQ', + 'LOG_AND', 'LOG_OR', 'MATH_DIV', 'MATH_MULT', + 'MATH_PLUS', 'MATH_MINUS', 'GT', 'LT', + 'GE', 'LE', 'LIKE', 'NOT_LIKE', + 'IN', 'NOT_IN', 'F_IF', 'F_ELT', + 'F_COALESCE', 'F_CONCAT', 'F_SUBSTR', 'F_TRIM', + 'F_DATE', 'F_DATE_FORMAT', 'F_CURRENT_DATE', 'F_NOW', + 'F_TIME', 'F_TO_DAYS', 'F_FROM_DAYS', 'F_DATE_ADD', + 'F_DATE_SUB', 'F_ROUND', 'F_FLOOR', 'F_INET_ATON', + 'F_INET_NTOA', 'error', 'result', 'query', + 'condition', 'class_name', 'join_statement', 'where_statement', + 'class_list', 'join_item', 'join_condition', 'field_id', + 'expression_prio4', 'expression_basic', 'scalar', 'var_name', + 'func_name', 'arg_list', 'list_operator', 'list', + 'expression_prio1', 'operator1', 'expression_prio2', 'operator2', + 'expression_prio3', 'operator3', 'operator4', 'scalar_list', + 'argument', 'interval_unit', 'num_scalar', 'str_scalar', + 'num_value', 'str_value', 'name', 'num_operator1', + 'num_operator2', 'str_operator', ); /** @@ -606,95 +612,99 @@ static public $yy_action = array( /* 1 */ "result ::= condition", /* 2 */ "query ::= SELECT class_name join_statement where_statement", /* 3 */ "query ::= SELECT class_name AS_ALIAS class_name join_statement where_statement", - /* 4 */ "where_statement ::= WHERE condition", - /* 5 */ "where_statement ::=", - /* 6 */ "join_statement ::= join_item join_statement", - /* 7 */ "join_statement ::= join_item", - /* 8 */ "join_statement ::=", - /* 9 */ "join_item ::= JOIN class_name AS_ALIAS class_name ON join_condition", - /* 10 */ "join_item ::= JOIN class_name ON join_condition", - /* 11 */ "join_condition ::= field_id EQ field_id", - /* 12 */ "condition ::= expression_prio4", - /* 13 */ "expression_basic ::= scalar", - /* 14 */ "expression_basic ::= field_id", - /* 15 */ "expression_basic ::= var_name", - /* 16 */ "expression_basic ::= func_name PAR_OPEN arg_list PAR_CLOSE", - /* 17 */ "expression_basic ::= PAR_OPEN expression_prio4 PAR_CLOSE", - /* 18 */ "expression_basic ::= expression_basic list_operator list", - /* 19 */ "expression_prio1 ::= expression_basic", - /* 20 */ "expression_prio1 ::= expression_prio1 operator1 expression_basic", - /* 21 */ "expression_prio2 ::= expression_prio1", - /* 22 */ "expression_prio2 ::= expression_prio2 operator2 expression_prio1", - /* 23 */ "expression_prio3 ::= expression_prio2", - /* 24 */ "expression_prio3 ::= expression_prio3 operator3 expression_prio2", - /* 25 */ "expression_prio4 ::= expression_prio3", - /* 26 */ "expression_prio4 ::= expression_prio4 operator4 expression_prio3", - /* 27 */ "list ::= PAR_OPEN scalar_list PAR_CLOSE", - /* 28 */ "scalar_list ::= scalar", - /* 29 */ "scalar_list ::= scalar_list COMA scalar", - /* 30 */ "arg_list ::=", - /* 31 */ "arg_list ::= argument", - /* 32 */ "arg_list ::= arg_list COMA argument", - /* 33 */ "argument ::= expression_prio4", - /* 34 */ "argument ::= INTERVAL expression_prio4 interval_unit", - /* 35 */ "interval_unit ::= F_SECOND", - /* 36 */ "interval_unit ::= F_MINUTE", - /* 37 */ "interval_unit ::= F_HOUR", - /* 38 */ "interval_unit ::= F_DAY", - /* 39 */ "interval_unit ::= F_MONTH", - /* 40 */ "interval_unit ::= F_YEAR", - /* 41 */ "scalar ::= num_scalar", - /* 42 */ "scalar ::= str_scalar", - /* 43 */ "num_scalar ::= num_value", - /* 44 */ "str_scalar ::= str_value", - /* 45 */ "field_id ::= name", - /* 46 */ "field_id ::= class_name DOT name", - /* 47 */ "class_name ::= name", - /* 48 */ "var_name ::= VARNAME", - /* 49 */ "name ::= NAME", - /* 50 */ "num_value ::= NUMVAL", - /* 51 */ "str_value ::= STRVAL", - /* 52 */ "operator1 ::= num_operator1", - /* 53 */ "operator2 ::= num_operator2", - /* 54 */ "operator2 ::= str_operator", - /* 55 */ "operator2 ::= EQ", - /* 56 */ "operator2 ::= NOT_EQ", - /* 57 */ "operator3 ::= LOG_AND", - /* 58 */ "operator4 ::= LOG_OR", - /* 59 */ "num_operator1 ::= MATH_DIV", - /* 60 */ "num_operator1 ::= MATH_MULT", - /* 61 */ "num_operator2 ::= MATH_PLUS", - /* 62 */ "num_operator2 ::= MATH_MINUS", - /* 63 */ "num_operator2 ::= GT", - /* 64 */ "num_operator2 ::= LT", - /* 65 */ "num_operator2 ::= GE", - /* 66 */ "num_operator2 ::= LE", - /* 67 */ "str_operator ::= LIKE", - /* 68 */ "str_operator ::= NOT_LIKE", - /* 69 */ "list_operator ::= IN", - /* 70 */ "list_operator ::= NOT_IN", - /* 71 */ "func_name ::= F_IF", - /* 72 */ "func_name ::= F_ELT", - /* 73 */ "func_name ::= F_COALESCE", - /* 74 */ "func_name ::= F_CONCAT", - /* 75 */ "func_name ::= F_SUBSTR", - /* 76 */ "func_name ::= F_TRIM", - /* 77 */ "func_name ::= F_DATE", - /* 78 */ "func_name ::= F_DATE_FORMAT", - /* 79 */ "func_name ::= F_CURRENT_DATE", - /* 80 */ "func_name ::= F_NOW", - /* 81 */ "func_name ::= F_TIME", - /* 82 */ "func_name ::= F_TO_DAYS", - /* 83 */ "func_name ::= F_FROM_DAYS", - /* 84 */ "func_name ::= F_YEAR", - /* 85 */ "func_name ::= F_MONTH", - /* 86 */ "func_name ::= F_DAY", - /* 87 */ "func_name ::= F_DATE_ADD", - /* 88 */ "func_name ::= F_DATE_SUB", - /* 89 */ "func_name ::= F_ROUND", - /* 90 */ "func_name ::= F_FLOOR", - /* 91 */ "func_name ::= F_INET_ATON", - /* 92 */ "func_name ::= F_INET_NTOA", + /* 4 */ "query ::= SELECT class_list FROM class_name join_statement where_statement", + /* 5 */ "query ::= SELECT class_list FROM class_name AS_ALIAS class_name join_statement where_statement", + /* 6 */ "class_list ::= class_name", + /* 7 */ "class_list ::= class_list COMA class_name", + /* 8 */ "where_statement ::= WHERE condition", + /* 9 */ "where_statement ::=", + /* 10 */ "join_statement ::= join_item join_statement", + /* 11 */ "join_statement ::= join_item", + /* 12 */ "join_statement ::=", + /* 13 */ "join_item ::= JOIN class_name AS_ALIAS class_name ON join_condition", + /* 14 */ "join_item ::= JOIN class_name ON join_condition", + /* 15 */ "join_condition ::= field_id EQ field_id", + /* 16 */ "condition ::= expression_prio4", + /* 17 */ "expression_basic ::= scalar", + /* 18 */ "expression_basic ::= field_id", + /* 19 */ "expression_basic ::= var_name", + /* 20 */ "expression_basic ::= func_name PAR_OPEN arg_list PAR_CLOSE", + /* 21 */ "expression_basic ::= PAR_OPEN expression_prio4 PAR_CLOSE", + /* 22 */ "expression_basic ::= expression_basic list_operator list", + /* 23 */ "expression_prio1 ::= expression_basic", + /* 24 */ "expression_prio1 ::= expression_prio1 operator1 expression_basic", + /* 25 */ "expression_prio2 ::= expression_prio1", + /* 26 */ "expression_prio2 ::= expression_prio2 operator2 expression_prio1", + /* 27 */ "expression_prio3 ::= expression_prio2", + /* 28 */ "expression_prio3 ::= expression_prio3 operator3 expression_prio2", + /* 29 */ "expression_prio4 ::= expression_prio3", + /* 30 */ "expression_prio4 ::= expression_prio4 operator4 expression_prio3", + /* 31 */ "list ::= PAR_OPEN scalar_list PAR_CLOSE", + /* 32 */ "scalar_list ::= scalar", + /* 33 */ "scalar_list ::= scalar_list COMA scalar", + /* 34 */ "arg_list ::=", + /* 35 */ "arg_list ::= argument", + /* 36 */ "arg_list ::= arg_list COMA argument", + /* 37 */ "argument ::= expression_prio4", + /* 38 */ "argument ::= INTERVAL expression_prio4 interval_unit", + /* 39 */ "interval_unit ::= F_SECOND", + /* 40 */ "interval_unit ::= F_MINUTE", + /* 41 */ "interval_unit ::= F_HOUR", + /* 42 */ "interval_unit ::= F_DAY", + /* 43 */ "interval_unit ::= F_MONTH", + /* 44 */ "interval_unit ::= F_YEAR", + /* 45 */ "scalar ::= num_scalar", + /* 46 */ "scalar ::= str_scalar", + /* 47 */ "num_scalar ::= num_value", + /* 48 */ "str_scalar ::= str_value", + /* 49 */ "field_id ::= name", + /* 50 */ "field_id ::= class_name DOT name", + /* 51 */ "class_name ::= name", + /* 52 */ "var_name ::= VARNAME", + /* 53 */ "name ::= NAME", + /* 54 */ "num_value ::= NUMVAL", + /* 55 */ "str_value ::= STRVAL", + /* 56 */ "operator1 ::= num_operator1", + /* 57 */ "operator2 ::= num_operator2", + /* 58 */ "operator2 ::= str_operator", + /* 59 */ "operator2 ::= EQ", + /* 60 */ "operator2 ::= NOT_EQ", + /* 61 */ "operator3 ::= LOG_AND", + /* 62 */ "operator4 ::= LOG_OR", + /* 63 */ "num_operator1 ::= MATH_DIV", + /* 64 */ "num_operator1 ::= MATH_MULT", + /* 65 */ "num_operator2 ::= MATH_PLUS", + /* 66 */ "num_operator2 ::= MATH_MINUS", + /* 67 */ "num_operator2 ::= GT", + /* 68 */ "num_operator2 ::= LT", + /* 69 */ "num_operator2 ::= GE", + /* 70 */ "num_operator2 ::= LE", + /* 71 */ "str_operator ::= LIKE", + /* 72 */ "str_operator ::= NOT_LIKE", + /* 73 */ "list_operator ::= IN", + /* 74 */ "list_operator ::= NOT_IN", + /* 75 */ "func_name ::= F_IF", + /* 76 */ "func_name ::= F_ELT", + /* 77 */ "func_name ::= F_COALESCE", + /* 78 */ "func_name ::= F_CONCAT", + /* 79 */ "func_name ::= F_SUBSTR", + /* 80 */ "func_name ::= F_TRIM", + /* 81 */ "func_name ::= F_DATE", + /* 82 */ "func_name ::= F_DATE_FORMAT", + /* 83 */ "func_name ::= F_CURRENT_DATE", + /* 84 */ "func_name ::= F_NOW", + /* 85 */ "func_name ::= F_TIME", + /* 86 */ "func_name ::= F_TO_DAYS", + /* 87 */ "func_name ::= F_FROM_DAYS", + /* 88 */ "func_name ::= F_YEAR", + /* 89 */ "func_name ::= F_MONTH", + /* 90 */ "func_name ::= F_DAY", + /* 91 */ "func_name ::= F_DATE_ADD", + /* 92 */ "func_name ::= F_DATE_SUB", + /* 93 */ "func_name ::= F_ROUND", + /* 94 */ "func_name ::= F_FLOOR", + /* 95 */ "func_name ::= F_INET_ATON", + /* 96 */ "func_name ::= F_INET_NTOA", ); /** @@ -1059,99 +1069,103 @@ static public $yy_action = array( * */ static public $yyRuleInfo = array( - array( 'lhs' => 57, 'rhs' => 1 ), - array( 'lhs' => 57, 'rhs' => 1 ), - array( 'lhs' => 58, 'rhs' => 4 ), - array( 'lhs' => 58, 'rhs' => 6 ), - array( 'lhs' => 62, 'rhs' => 2 ), - array( 'lhs' => 62, 'rhs' => 0 ), - array( 'lhs' => 61, 'rhs' => 2 ), - array( 'lhs' => 61, 'rhs' => 1 ), - array( 'lhs' => 61, 'rhs' => 0 ), - array( 'lhs' => 63, 'rhs' => 6 ), - array( 'lhs' => 63, 'rhs' => 4 ), + array( 'lhs' => 58, 'rhs' => 1 ), + array( 'lhs' => 58, 'rhs' => 1 ), + array( 'lhs' => 59, 'rhs' => 4 ), + array( 'lhs' => 59, 'rhs' => 6 ), + array( 'lhs' => 59, 'rhs' => 6 ), + array( 'lhs' => 59, 'rhs' => 8 ), + array( 'lhs' => 64, 'rhs' => 1 ), array( 'lhs' => 64, 'rhs' => 3 ), - array( 'lhs' => 59, 'rhs' => 1 ), - array( 'lhs' => 67, 'rhs' => 1 ), - array( 'lhs' => 67, 'rhs' => 1 ), - array( 'lhs' => 67, 'rhs' => 1 ), - array( 'lhs' => 67, 'rhs' => 4 ), - array( 'lhs' => 67, 'rhs' => 3 ), - array( 'lhs' => 67, 'rhs' => 3 ), - array( 'lhs' => 74, 'rhs' => 1 ), - array( 'lhs' => 74, 'rhs' => 3 ), + array( 'lhs' => 63, 'rhs' => 2 ), + array( 'lhs' => 63, 'rhs' => 0 ), + array( 'lhs' => 62, 'rhs' => 2 ), + array( 'lhs' => 62, 'rhs' => 1 ), + array( 'lhs' => 62, 'rhs' => 0 ), + array( 'lhs' => 65, 'rhs' => 6 ), + array( 'lhs' => 65, 'rhs' => 4 ), + array( 'lhs' => 66, 'rhs' => 3 ), + array( 'lhs' => 60, 'rhs' => 1 ), + array( 'lhs' => 69, 'rhs' => 1 ), + array( 'lhs' => 69, 'rhs' => 1 ), + array( 'lhs' => 69, 'rhs' => 1 ), + array( 'lhs' => 69, 'rhs' => 4 ), + array( 'lhs' => 69, 'rhs' => 3 ), + array( 'lhs' => 69, 'rhs' => 3 ), array( 'lhs' => 76, 'rhs' => 1 ), array( 'lhs' => 76, 'rhs' => 3 ), array( 'lhs' => 78, 'rhs' => 1 ), array( 'lhs' => 78, 'rhs' => 3 ), - array( 'lhs' => 66, 'rhs' => 1 ), - array( 'lhs' => 66, 'rhs' => 3 ), + array( 'lhs' => 80, 'rhs' => 1 ), + array( 'lhs' => 80, 'rhs' => 3 ), + array( 'lhs' => 68, 'rhs' => 1 ), + array( 'lhs' => 68, 'rhs' => 3 ), + array( 'lhs' => 75, 'rhs' => 3 ), + array( 'lhs' => 83, 'rhs' => 1 ), + array( 'lhs' => 83, 'rhs' => 3 ), + array( 'lhs' => 73, 'rhs' => 0 ), + array( 'lhs' => 73, 'rhs' => 1 ), array( 'lhs' => 73, 'rhs' => 3 ), - array( 'lhs' => 81, 'rhs' => 1 ), - array( 'lhs' => 81, 'rhs' => 3 ), - array( 'lhs' => 71, 'rhs' => 0 ), - array( 'lhs' => 71, 'rhs' => 1 ), - array( 'lhs' => 71, 'rhs' => 3 ), - array( 'lhs' => 82, 'rhs' => 1 ), - array( 'lhs' => 82, 'rhs' => 3 ), - array( 'lhs' => 83, 'rhs' => 1 ), - array( 'lhs' => 83, 'rhs' => 1 ), - array( 'lhs' => 83, 'rhs' => 1 ), - array( 'lhs' => 83, 'rhs' => 1 ), - array( 'lhs' => 83, 'rhs' => 1 ), - array( 'lhs' => 83, 'rhs' => 1 ), - array( 'lhs' => 68, 'rhs' => 1 ), - array( 'lhs' => 68, 'rhs' => 1 ), array( 'lhs' => 84, 'rhs' => 1 ), + array( 'lhs' => 84, 'rhs' => 3 ), array( 'lhs' => 85, 'rhs' => 1 ), - array( 'lhs' => 65, 'rhs' => 1 ), - array( 'lhs' => 65, 'rhs' => 3 ), - array( 'lhs' => 60, 'rhs' => 1 ), - array( 'lhs' => 69, 'rhs' => 1 ), - array( 'lhs' => 88, 'rhs' => 1 ), + array( 'lhs' => 85, 'rhs' => 1 ), + array( 'lhs' => 85, 'rhs' => 1 ), + array( 'lhs' => 85, 'rhs' => 1 ), + array( 'lhs' => 85, 'rhs' => 1 ), + array( 'lhs' => 85, 'rhs' => 1 ), + array( 'lhs' => 70, 'rhs' => 1 ), + array( 'lhs' => 70, 'rhs' => 1 ), array( 'lhs' => 86, 'rhs' => 1 ), array( 'lhs' => 87, 'rhs' => 1 ), - array( 'lhs' => 75, 'rhs' => 1 ), - array( 'lhs' => 77, 'rhs' => 1 ), - array( 'lhs' => 77, 'rhs' => 1 ), - array( 'lhs' => 77, 'rhs' => 1 ), + array( 'lhs' => 67, 'rhs' => 1 ), + array( 'lhs' => 67, 'rhs' => 3 ), + array( 'lhs' => 61, 'rhs' => 1 ), + array( 'lhs' => 71, 'rhs' => 1 ), + array( 'lhs' => 90, 'rhs' => 1 ), + array( 'lhs' => 88, 'rhs' => 1 ), + array( 'lhs' => 89, 'rhs' => 1 ), array( 'lhs' => 77, 'rhs' => 1 ), array( 'lhs' => 79, 'rhs' => 1 ), - array( 'lhs' => 80, 'rhs' => 1 ), - array( 'lhs' => 89, 'rhs' => 1 ), - array( 'lhs' => 89, 'rhs' => 1 ), - array( 'lhs' => 90, 'rhs' => 1 ), - array( 'lhs' => 90, 'rhs' => 1 ), - array( 'lhs' => 90, 'rhs' => 1 ), - array( 'lhs' => 90, 'rhs' => 1 ), - array( 'lhs' => 90, 'rhs' => 1 ), - array( 'lhs' => 90, 'rhs' => 1 ), + array( 'lhs' => 79, 'rhs' => 1 ), + array( 'lhs' => 79, 'rhs' => 1 ), + array( 'lhs' => 79, 'rhs' => 1 ), + array( 'lhs' => 81, 'rhs' => 1 ), + array( 'lhs' => 82, 'rhs' => 1 ), array( 'lhs' => 91, 'rhs' => 1 ), array( 'lhs' => 91, 'rhs' => 1 ), + array( 'lhs' => 92, 'rhs' => 1 ), + array( 'lhs' => 92, 'rhs' => 1 ), + array( 'lhs' => 92, 'rhs' => 1 ), + array( 'lhs' => 92, 'rhs' => 1 ), + array( 'lhs' => 92, 'rhs' => 1 ), + array( 'lhs' => 92, 'rhs' => 1 ), + array( 'lhs' => 93, 'rhs' => 1 ), + array( 'lhs' => 93, 'rhs' => 1 ), + array( 'lhs' => 74, 'rhs' => 1 ), + array( 'lhs' => 74, 'rhs' => 1 ), + array( 'lhs' => 72, 'rhs' => 1 ), + array( 'lhs' => 72, 'rhs' => 1 ), + array( 'lhs' => 72, 'rhs' => 1 ), + array( 'lhs' => 72, 'rhs' => 1 ), + array( 'lhs' => 72, 'rhs' => 1 ), + array( 'lhs' => 72, 'rhs' => 1 ), + array( 'lhs' => 72, 'rhs' => 1 ), + array( 'lhs' => 72, 'rhs' => 1 ), + array( 'lhs' => 72, 'rhs' => 1 ), + array( 'lhs' => 72, 'rhs' => 1 ), + array( 'lhs' => 72, 'rhs' => 1 ), + array( 'lhs' => 72, 'rhs' => 1 ), + array( 'lhs' => 72, 'rhs' => 1 ), + array( 'lhs' => 72, 'rhs' => 1 ), + array( 'lhs' => 72, 'rhs' => 1 ), + array( 'lhs' => 72, 'rhs' => 1 ), + array( 'lhs' => 72, 'rhs' => 1 ), + array( 'lhs' => 72, 'rhs' => 1 ), + array( 'lhs' => 72, 'rhs' => 1 ), + array( 'lhs' => 72, 'rhs' => 1 ), array( 'lhs' => 72, 'rhs' => 1 ), array( 'lhs' => 72, 'rhs' => 1 ), - array( 'lhs' => 70, 'rhs' => 1 ), - array( 'lhs' => 70, 'rhs' => 1 ), - array( 'lhs' => 70, 'rhs' => 1 ), - array( 'lhs' => 70, 'rhs' => 1 ), - array( 'lhs' => 70, 'rhs' => 1 ), - array( 'lhs' => 70, 'rhs' => 1 ), - array( 'lhs' => 70, 'rhs' => 1 ), - array( 'lhs' => 70, 'rhs' => 1 ), - array( 'lhs' => 70, 'rhs' => 1 ), - array( 'lhs' => 70, 'rhs' => 1 ), - array( 'lhs' => 70, 'rhs' => 1 ), - array( 'lhs' => 70, 'rhs' => 1 ), - array( 'lhs' => 70, 'rhs' => 1 ), - array( 'lhs' => 70, 'rhs' => 1 ), - array( 'lhs' => 70, 'rhs' => 1 ), - array( 'lhs' => 70, 'rhs' => 1 ), - array( 'lhs' => 70, 'rhs' => 1 ), - array( 'lhs' => 70, 'rhs' => 1 ), - array( 'lhs' => 70, 'rhs' => 1 ), - array( 'lhs' => 70, 'rhs' => 1 ), - array( 'lhs' => 70, 'rhs' => 1 ), - array( 'lhs' => 70, 'rhs' => 1 ), ); /** @@ -1167,93 +1181,97 @@ static public $yy_action = array( 3 => 3, 4 => 4, 5 => 5, - 8 => 5, 6 => 6, + 32 => 6, + 35 => 6, 7 => 7, + 33 => 7, + 36 => 7, + 8 => 8, 9 => 9, + 12 => 9, 10 => 10, 11 => 11, - 12 => 12, - 13 => 12, - 14 => 12, - 15 => 12, - 19 => 12, - 21 => 12, - 23 => 12, - 25 => 12, - 33 => 12, - 35 => 12, - 36 => 12, - 37 => 12, - 38 => 12, - 39 => 12, - 40 => 12, - 41 => 12, - 42 => 12, + 13 => 13, + 14 => 14, + 15 => 15, 16 => 16, - 17 => 17, - 18 => 18, - 20 => 18, - 22 => 18, - 24 => 18, - 26 => 18, - 27 => 27, - 28 => 28, - 31 => 28, - 29 => 29, - 32 => 29, - 30 => 30, + 17 => 16, + 18 => 16, + 19 => 16, + 23 => 16, + 25 => 16, + 27 => 16, + 29 => 16, + 37 => 16, + 39 => 16, + 40 => 16, + 41 => 16, + 42 => 16, + 43 => 16, + 44 => 16, + 45 => 16, + 46 => 16, + 20 => 20, + 21 => 21, + 22 => 22, + 24 => 22, + 26 => 22, + 28 => 22, + 30 => 22, + 31 => 31, 34 => 34, - 43 => 43, - 44 => 43, - 45 => 45, - 46 => 46, + 38 => 38, 47 => 47, - 71 => 47, - 72 => 47, - 73 => 47, - 74 => 47, - 75 => 47, - 76 => 47, - 77 => 47, - 78 => 47, - 79 => 47, - 80 => 47, - 81 => 47, - 82 => 47, - 83 => 47, - 84 => 47, - 85 => 47, - 86 => 47, - 87 => 47, - 88 => 47, - 89 => 47, - 90 => 47, - 91 => 47, - 92 => 47, - 48 => 48, + 48 => 47, 49 => 49, 50 => 50, - 52 => 50, - 53 => 50, - 54 => 50, - 55 => 50, - 56 => 50, - 57 => 50, - 58 => 50, - 59 => 50, - 60 => 50, - 61 => 50, - 62 => 50, - 63 => 50, - 64 => 50, - 65 => 50, - 66 => 50, - 67 => 50, - 68 => 50, - 69 => 50, - 70 => 50, 51 => 51, + 75 => 51, + 76 => 51, + 77 => 51, + 78 => 51, + 79 => 51, + 80 => 51, + 81 => 51, + 82 => 51, + 83 => 51, + 84 => 51, + 85 => 51, + 86 => 51, + 87 => 51, + 88 => 51, + 89 => 51, + 90 => 51, + 91 => 51, + 92 => 51, + 93 => 51, + 94 => 51, + 95 => 51, + 96 => 51, + 52 => 52, + 53 => 53, + 54 => 54, + 56 => 54, + 57 => 54, + 58 => 54, + 59 => 54, + 60 => 54, + 61 => 54, + 62 => 54, + 63 => 54, + 64 => 54, + 65 => 54, + 66 => 54, + 67 => 54, + 68 => 54, + 69 => 54, + 70 => 54, + 71 => 54, + 72 => 54, + 73 => 54, + 74 => 54, + 55 => 55, ); /* Beginning here are the reduction cases. A typical example ** follows: @@ -1263,104 +1281,114 @@ static public $yy_action = array( */ #line 29 "oql-parser.y" function yy_r0(){ $this->my_result = $this->yystack[$this->yyidx + 0]->minor; } -#line 1270 "oql-parser.php" +#line 1288 "oql-parser.php" #line 32 "oql-parser.y" function yy_r2(){ - $this->_retvalue = new OqlObjectQuery($this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + 0]->minor, $this->yystack[$this->yyidx + -1]->minor); + $this->_retvalue = new OqlObjectQuery($this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + 0]->minor, $this->yystack[$this->yyidx + -1]->minor, array($this->yystack[$this->yyidx + -2]->minor)); } -#line 1275 "oql-parser.php" +#line 1293 "oql-parser.php" #line 35 "oql-parser.y" function yy_r3(){ - $this->_retvalue = new OqlObjectQuery($this->yystack[$this->yyidx + -4]->minor, $this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + 0]->minor, $this->yystack[$this->yyidx + -1]->minor); + $this->_retvalue = new OqlObjectQuery($this->yystack[$this->yyidx + -4]->minor, $this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + 0]->minor, $this->yystack[$this->yyidx + -1]->minor, array($this->yystack[$this->yyidx + -2]->minor)); } -#line 1280 "oql-parser.php" -#line 48 "oql-parser.y" - function yy_r4(){ $this->_retvalue = $this->yystack[$this->yyidx + 0]->minor; } -#line 1283 "oql-parser.php" -#line 49 "oql-parser.y" - function yy_r5(){ $this->_retvalue = null; } -#line 1286 "oql-parser.php" -#line 51 "oql-parser.y" +#line 1298 "oql-parser.php" +#line 39 "oql-parser.y" + function yy_r4(){ + $this->_retvalue = new OqlObjectQuery($this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + 0]->minor, $this->yystack[$this->yyidx + -1]->minor, $this->yystack[$this->yyidx + -4]->minor); + } +#line 1303 "oql-parser.php" +#line 42 "oql-parser.y" + function yy_r5(){ + $this->_retvalue = new OqlObjectQuery($this->yystack[$this->yyidx + -4]->minor, $this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + 0]->minor, $this->yystack[$this->yyidx + -1]->minor, $this->yystack[$this->yyidx + -6]->minor); + } +#line 1308 "oql-parser.php" +#line 47 "oql-parser.y" function yy_r6(){ + $this->_retvalue = array($this->yystack[$this->yyidx + 0]->minor); + } +#line 1313 "oql-parser.php" +#line 50 "oql-parser.y" + function yy_r7(){ + array_push($this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + 0]->minor); + $this->_retvalue = $this->yystack[$this->yyidx + -2]->minor; + } +#line 1319 "oql-parser.php" +#line 55 "oql-parser.y" + function yy_r8(){ $this->_retvalue = $this->yystack[$this->yyidx + 0]->minor; } +#line 1322 "oql-parser.php" +#line 56 "oql-parser.y" + function yy_r9(){ $this->_retvalue = null; } +#line 1325 "oql-parser.php" +#line 58 "oql-parser.y" + function yy_r10(){ // insert the join statement on top of the existing list array_unshift($this->yystack[$this->yyidx + 0]->minor, $this->yystack[$this->yyidx + -1]->minor); // and return the updated array $this->_retvalue = $this->yystack[$this->yyidx + 0]->minor; } -#line 1294 "oql-parser.php" -#line 57 "oql-parser.y" - function yy_r7(){ +#line 1333 "oql-parser.php" +#line 64 "oql-parser.y" + function yy_r11(){ $this->_retvalue = Array($this->yystack[$this->yyidx + 0]->minor); } -#line 1299 "oql-parser.php" -#line 63 "oql-parser.y" - function yy_r9(){ +#line 1338 "oql-parser.php" +#line 70 "oql-parser.y" + function yy_r13(){ // create an array with one single item $this->_retvalue = new OqlJoinSpec($this->yystack[$this->yyidx + -4]->minor, $this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + 0]->minor); } -#line 1305 "oql-parser.php" -#line 68 "oql-parser.y" - function yy_r10(){ +#line 1344 "oql-parser.php" +#line 75 "oql-parser.y" + function yy_r14(){ // create an array with one single item $this->_retvalue = new OqlJoinSpec($this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + 0]->minor); } -#line 1311 "oql-parser.php" -#line 73 "oql-parser.y" - function yy_r11(){ $this->_retvalue = new BinaryOqlExpression($this->yystack[$this->yyidx + -2]->minor, '=', $this->yystack[$this->yyidx + 0]->minor); } -#line 1314 "oql-parser.php" -#line 75 "oql-parser.y" - function yy_r12(){ $this->_retvalue = $this->yystack[$this->yyidx + 0]->minor; } -#line 1317 "oql-parser.php" +#line 1350 "oql-parser.php" #line 80 "oql-parser.y" - function yy_r16(){ $this->_retvalue = new FunctionOqlExpression($this->yystack[$this->yyidx + -3]->minor, $this->yystack[$this->yyidx + -1]->minor); } -#line 1320 "oql-parser.php" -#line 81 "oql-parser.y" - function yy_r17(){ $this->_retvalue = $this->yystack[$this->yyidx + -1]->minor; } -#line 1323 "oql-parser.php" + function yy_r15(){ $this->_retvalue = new BinaryOqlExpression($this->yystack[$this->yyidx + -2]->minor, '=', $this->yystack[$this->yyidx + 0]->minor); } +#line 1353 "oql-parser.php" #line 82 "oql-parser.y" - function yy_r18(){ $this->_retvalue = new BinaryOqlExpression($this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + -1]->minor, $this->yystack[$this->yyidx + 0]->minor); } -#line 1326 "oql-parser.php" -#line 97 "oql-parser.y" - function yy_r27(){ + function yy_r16(){ $this->_retvalue = $this->yystack[$this->yyidx + 0]->minor; } +#line 1356 "oql-parser.php" +#line 87 "oql-parser.y" + function yy_r20(){ $this->_retvalue = new FunctionOqlExpression($this->yystack[$this->yyidx + -3]->minor, $this->yystack[$this->yyidx + -1]->minor); } +#line 1359 "oql-parser.php" +#line 88 "oql-parser.y" + function yy_r21(){ $this->_retvalue = $this->yystack[$this->yyidx + -1]->minor; } +#line 1362 "oql-parser.php" +#line 89 "oql-parser.y" + function yy_r22(){ $this->_retvalue = new BinaryOqlExpression($this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + -1]->minor, $this->yystack[$this->yyidx + 0]->minor); } +#line 1365 "oql-parser.php" +#line 104 "oql-parser.y" + function yy_r31(){ $this->_retvalue = new ListOqlExpression($this->yystack[$this->yyidx + -1]->minor); } -#line 1331 "oql-parser.php" -#line 100 "oql-parser.y" - function yy_r28(){ - $this->_retvalue = array($this->yystack[$this->yyidx + 0]->minor); - } -#line 1336 "oql-parser.php" -#line 103 "oql-parser.y" - function yy_r29(){ - array_push($this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + 0]->minor); - $this->_retvalue = $this->yystack[$this->yyidx + -2]->minor; - } -#line 1342 "oql-parser.php" -#line 108 "oql-parser.y" - function yy_r30(){ +#line 1370 "oql-parser.php" +#line 115 "oql-parser.y" + function yy_r34(){ $this->_retvalue = array(); } -#line 1347 "oql-parser.php" -#line 119 "oql-parser.y" - function yy_r34(){ $this->_retvalue = new IntervalOqlExpression($this->yystack[$this->yyidx + -1]->minor, $this->yystack[$this->yyidx + 0]->minor); } -#line 1350 "oql-parser.php" -#line 131 "oql-parser.y" - function yy_r43(){ $this->_retvalue = new ScalarOqlExpression($this->yystack[$this->yyidx + 0]->minor); } -#line 1353 "oql-parser.php" -#line 134 "oql-parser.y" - function yy_r45(){ $this->_retvalue = new FieldOqlExpression($this->yystack[$this->yyidx + 0]->minor); } -#line 1356 "oql-parser.php" -#line 135 "oql-parser.y" - function yy_r46(){ $this->_retvalue = new FieldOqlExpression($this->yystack[$this->yyidx + 0]->minor, $this->yystack[$this->yyidx + -2]->minor); } -#line 1359 "oql-parser.php" -#line 136 "oql-parser.y" - function yy_r47(){ $this->_retvalue=$this->yystack[$this->yyidx + 0]->minor; } -#line 1362 "oql-parser.php" -#line 139 "oql-parser.y" - function yy_r48(){ $this->_retvalue = new VariableOqlExpression(substr($this->yystack[$this->yyidx + 0]->minor, 1)); } -#line 1365 "oql-parser.php" +#line 1375 "oql-parser.php" +#line 126 "oql-parser.y" + function yy_r38(){ $this->_retvalue = new IntervalOqlExpression($this->yystack[$this->yyidx + -1]->minor, $this->yystack[$this->yyidx + 0]->minor); } +#line 1378 "oql-parser.php" +#line 138 "oql-parser.y" + function yy_r47(){ $this->_retvalue = new ScalarOqlExpression($this->yystack[$this->yyidx + 0]->minor); } +#line 1381 "oql-parser.php" #line 141 "oql-parser.y" - function yy_r49(){ + function yy_r49(){ $this->_retvalue = new FieldOqlExpression($this->yystack[$this->yyidx + 0]->minor); } +#line 1384 "oql-parser.php" +#line 142 "oql-parser.y" + function yy_r50(){ $this->_retvalue = new FieldOqlExpression($this->yystack[$this->yyidx + 0]->minor, $this->yystack[$this->yyidx + -2]->minor); } +#line 1387 "oql-parser.php" +#line 143 "oql-parser.y" + function yy_r51(){ $this->_retvalue=$this->yystack[$this->yyidx + 0]->minor; } +#line 1390 "oql-parser.php" +#line 146 "oql-parser.y" + function yy_r52(){ $this->_retvalue = new VariableOqlExpression(substr($this->yystack[$this->yyidx + 0]->minor, 1)); } +#line 1393 "oql-parser.php" +#line 148 "oql-parser.y" + function yy_r53(){ if ($this->yystack[$this->yyidx + 0]->minor[0] == '`') { $name = substr($this->yystack[$this->yyidx + 0]->minor, 1, strlen($this->yystack[$this->yyidx + 0]->minor) - 2); @@ -1371,13 +1399,13 @@ static public $yy_action = array( } $this->_retvalue = new OqlName($name, $this->m_iColPrev); } -#line 1378 "oql-parser.php" -#line 153 "oql-parser.y" - function yy_r50(){$this->_retvalue=$this->yystack[$this->yyidx + 0]->minor; } -#line 1381 "oql-parser.php" -#line 154 "oql-parser.y" - function yy_r51(){$this->_retvalue=stripslashes(substr($this->yystack[$this->yyidx + 0]->minor, 1, strlen($this->yystack[$this->yyidx + 0]->minor) - 2)); } -#line 1384 "oql-parser.php" +#line 1406 "oql-parser.php" +#line 160 "oql-parser.y" + function yy_r54(){$this->_retvalue=$this->yystack[$this->yyidx + 0]->minor; } +#line 1409 "oql-parser.php" +#line 161 "oql-parser.y" + function yy_r55(){$this->_retvalue=stripslashes(substr($this->yystack[$this->yyidx + 0]->minor, 1, strlen($this->yystack[$this->yyidx + 0]->minor) - 2)); } +#line 1412 "oql-parser.php" /** * placeholder for the left hand side in a reduce operation. @@ -1492,7 +1520,7 @@ static public $yy_action = array( #line 25 "oql-parser.y" throw new OQLParserException($this->m_sSourceQuery, $this->m_iLine, $this->m_iCol, $this->tokenName($yymajor), $TOKEN); -#line 1500 "oql-parser.php" +#line 1528 "oql-parser.php" } /** @@ -1644,7 +1672,7 @@ throw new OQLParserException($this->m_sSourceQuery, $this->m_iLine, $this->m_iCo } } while ($yymajor != self::YYNOCODE && $this->yyidx >= 0); } -}#line 204 "oql-parser.y" +}#line 211 "oql-parser.y" class OQLParserException extends OQLException @@ -1709,4 +1737,4 @@ class OQLParser extends OQLParserRaw } } -#line 1719 "oql-parser.php" +#line 1747 "oql-parser.php" diff --git a/core/oql/oql-parser.y b/core/oql/oql-parser.y index e432e7a39..ee28b9728 100644 --- a/core/oql/oql-parser.y +++ b/core/oql/oql-parser.y @@ -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;} diff --git a/core/oql/oqlinterpreter.class.inc.php b/core/oql/oqlinterpreter.class.inc.php index ef55d0309..2a8ffe359 100644 --- a/core/oql/oqlinterpreter.class.inc.php +++ b/core/oql/oqlinterpreter.class.inc.php @@ -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(); diff --git a/core/oql/oqlquery.class.inc.php b/core/oql/oqlquery.class.inc.php index bec211037..1979cdac5 100644 --- a/core/oql/oqlquery.class.inc.php +++ b/core/oql/oqlquery.class.inc.php @@ -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; - } -} ?> diff --git a/pages/testlist.inc.php b/pages/testlist.inc.php index 137fad97d..2e439c8e0 100644 --- a/pages/testlist.inc.php +++ b/pages/testlist.inc.php @@ -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,