From 903ae8a581fac1e6917fc3e31d2ad8fb91df80c3 Mon Sep 17 00:00:00 2001 From: ctrlaltca <> Date: Thu, 12 Jul 2012 11:21:01 +0000 Subject: standardize the use of unix eol; use svn properties to enforce native eol --- .../Data/SqlMap/Statements/TMappedStatement.php | 2484 ++++++++++---------- 1 file changed, 1242 insertions(+), 1242 deletions(-) (limited to 'framework/Data/SqlMap/Statements/TMappedStatement.php') diff --git a/framework/Data/SqlMap/Statements/TMappedStatement.php b/framework/Data/SqlMap/Statements/TMappedStatement.php index 0dbc8def..ee54df95 100644 --- a/framework/Data/SqlMap/Statements/TMappedStatement.php +++ b/framework/Data/SqlMap/Statements/TMappedStatement.php @@ -1,1242 +1,1242 @@ - - * @link http://www.pradosoft.com/ - * @copyright Copyright © 2005-2012 PradoSoft - * @license http://www.pradosoft.com/license/ - * @version $Id$ - * @package System.Data.SqlMap.Statements - */ - -/** - * TMappedStatement class executes SQL mapped statements. Mapped Statements can - * hold any SQL statement and use Parameter Maps and Result Maps for input and output. - * - * This class is usualy instantiated during SQLMap configuration by TSqlDomBuilder. - * - * @author Wei Zhuo - * @version $Id$ - * @package System.Data.SqlMap.Statements - * @since 3.0 - */ -class TMappedStatement extends TComponent implements IMappedStatement -{ - /** - * @var TSqlMapStatement current SQL statement. - */ - private $_statement; - - /** - * @var TPreparedCommand SQL command prepareer - */ - private $_command; - - /** - * @var TSqlMapper sqlmap used by this mapper. - */ - private $_manager; - - /** - * @var TPostSelectBinding[] post select statement queue. - */ - private $_selectQueue=array(); - - /** - * @var boolean true when data is mapped to a particular row. - */ - private $_IsRowDataFound = false; - - /** - * @var TSQLMapObjectCollectionTree group by object collection tree - */ - private $_groupBy; - - /** - * @var Post select is to query for list. - */ - const QUERY_FOR_LIST = 0; - - /** - * @var Post select is to query for list. - */ - const QUERY_FOR_ARRAY = 1; - - /** - * @var Post select is to query for object. - */ - const QUERY_FOR_OBJECT = 2; - - /** - * @return string Name used to identify the TMappedStatement amongst the others. - * This the name of the SQL statement by default. - */ - public function getID() - { - return $this->_statement->ID; - } - - /** - * @return TSqlMapStatement The SQL statment used by this MappedStatement - */ - public function getStatement() - { - return $this->_statement; - } - - /** - * @return TSqlMapper The SqlMap used by this MappedStatement - */ - public function getManager() - { - return $this->_manager; - } - - /** - * @return TPreparedCommand command to prepare SQL statements. - */ - public function getCommand() - { - return $this->_command; - } - - /** - * Empty the group by results cache. - */ - protected function initialGroupByResults() - { - $this->_groupBy = new TSqlMapObjectCollectionTree(); - } - - /** - * Creates a new mapped statement. - * @param TSqlMapper an sqlmap. - * @param TSqlMapStatement An SQL statement. - */ - public function __construct(TSqlMapManager $sqlMap, TSqlMapStatement $statement) - { - $this->_manager = $sqlMap; - $this->_statement = $statement; - $this->_command = new TPreparedCommand(); - $this->initialGroupByResults(); - } - - public function getSqlString() - { - return $this->getStatement()->getSqlText()->getPreparedStatement()->getPreparedSql(); - } - - /** - * Execute SQL Query. - * @param IDbConnection database connection - * @param array SQL statement and parameters. - * @return mixed record set if applicable. - * @throws TSqlMapExecutionException if execution error or false record set. - * @throws TSqlMapQueryExecutionException if any execution error - */ -/* protected function executeSQLQuery($connection, $sql) - { - try - { - if(!($recordSet = $connection->execute($sql['sql'],$sql['parameters']))) - { - throw new TSqlMapExecutionException( - 'sqlmap_execution_error_no_record', $this->getID(), - $connection->ErrorMsg()); - } - return $recordSet; - } - catch (Exception $e) - { - throw new TSqlMapQueryExecutionException($this->getStatement(), $e); - } - }*/ - - /** - * Execute SQL Query with limits. - * @param IDbConnection database connection - * @param array SQL statement and parameters. - * @return mixed record set if applicable. - * @throws TSqlMapExecutionException if execution error or false record set. - * @throws TSqlMapQueryExecutionException if any execution error - */ - protected function executeSQLQueryLimit($connection, $command, $max, $skip) - { - if($max>-1 || $skip > -1) - { - $maxStr=$max>0?' LIMIT '.$max:''; - $skipStr=$skip>0?' OFFSET '.$skip:''; - $command->setText($command->getText().$maxStr.$skipStr); - } - $connection->setActive(true); - return $command->query(); - - /*//var_dump($command); - try - { - $recordSet = $connection->selectLimit($sql['sql'],$max,$skip,$sql['parameters']); - if(!$recordSet) - { - throw new TSqlMapExecutionException( - 'sqlmap_execution_error_query_for_list', - $connection->ErrorMsg()); - } - return $recordSet; - } - catch (Exception $e) - { - throw new TSqlMapQueryExecutionException($this->getStatement(), $e); - }*/ - } - - /** - * Executes the SQL and retuns a List of result objects. - * @param IDbConnection database connection - * @param mixed The object used to set the parameters in the SQL. - * @param object result collection object. - * @param integer The number of rows to skip over. - * @param integer The maximum number of rows to return. - * @return array a list of result objects - * @param callback row delegate handler - * @see executeQueryForList() - */ - public function executeQueryForList($connection, $parameter, $result=null, $skip=-1, $max=-1, $delegate=null) - { - $sql = $this->_command->create($this->_manager, $connection, $this->_statement, $parameter,$skip,$max); - return $this->runQueryForList($connection, $parameter, $sql, $result, $delegate); - } - - /** - * Executes the SQL and retuns a List of result objects. - * - * This method should only be called by internal developers, consider using - * executeQueryForList() first. - * - * @param IDbConnection database connection - * @param mixed The object used to set the parameters in the SQL. - * @param array SQL string and subsititution parameters. - * @param object result collection object. - * @param integer The number of rows to skip over. - * @param integer The maximum number of rows to return. - * @param callback row delegate handler - * @return array a list of result objects - * @see executeQueryForList() - */ - public function runQueryForList($connection, $parameter, $sql, $result, $delegate=null) - { - $registry=$this->getManager()->getTypeHandlers(); - $list = $result instanceof ArrayAccess ? $result : - $this->_statement->createInstanceOfListClass($registry); - $connection->setActive(true); - $reader = $sql->query(); - //$reader = $this->executeSQLQueryLimit($connection, $sql, $max, $skip); - if($delegate!==null) - { - foreach($reader as $row) - { - $obj = $this->applyResultMap($row); - $param = new TResultSetListItemParameter($obj, $parameter, $list); - $this->raiseRowDelegate($delegate, $param); - } - } - else - { - //var_dump($sql,$parameter); - foreach($reader as $row) - { -// var_dump($row); - $list[] = $this->applyResultMap($row); - } - } - - if(!$this->_groupBy->isEmpty()) - { - $list = $this->_groupBy->collect(); - $this->initialGroupByResults(); - } - - $this->executePostSelect($connection); - $this->onExecuteQuery($sql); - - return $list; - } - - /** - * Executes the SQL and retuns all rows selected in a map that is keyed on - * the property named in the keyProperty parameter. The value at each key - * will be the value of the property specified in the valueProperty parameter. - * If valueProperty is null, the entire result object will be entered. - * @param IDbConnection database connection - * @param mixed The object used to set the parameters in the SQL. - * @param string The property of the result object to be used as the key. - * @param string The property of the result object to be used as the value (or null). - * @param callback row delegate handler - * @return array An array of object containing the rows keyed by keyProperty. - */ - public function executeQueryForMap($connection, $parameter, $keyProperty, $valueProperty=null, $skip=-1, $max=-1, $delegate=null) - { - $sql = $this->_command->create($this->_manager, $connection, $this->_statement, $parameter, $skip, $max); - return $this->runQueryForMap($connection, $parameter, $sql, $keyProperty, $valueProperty, $delegate); - } - - /** - * Executes the SQL and retuns all rows selected in a map that is keyed on - * the property named in the keyProperty parameter. The value at each key - * will be the value of the property specified in the valueProperty parameter. - * If valueProperty is null, the entire result object will be entered. - * - * This method should only be called by internal developers, consider using - * executeQueryForMap() first. - * - * @param IDbConnection database connection - * @param mixed The object used to set the parameters in the SQL. - * @param array SQL string and subsititution parameters. - * @param string The property of the result object to be used as the key. - * @param string The property of the result object to be used as the value (or null). - * @param callback row delegate, a callback function - * @return array An array of object containing the rows keyed by keyProperty. - * @see executeQueryForMap() - */ - public function runQueryForMap($connection, $parameter, $command, $keyProperty, $valueProperty=null, $delegate=null) - { - $map = array(); - //$recordSet = $this->executeSQLQuery($connection, $sql); - $connection->setActive(true); - $reader = $command->query(); - if($delegate!==null) - { - //while($row = $recordSet->fetchRow()) - foreach($reader as $row) - { - $obj = $this->applyResultMap($row); - $key = TPropertyAccess::get($obj, $keyProperty); - $value = ($valueProperty===null) ? $obj : - TPropertyAccess::get($obj, $valueProperty); - $param = new TResultSetMapItemParameter($key, $value, $parameter, $map); - $this->raiseRowDelegate($delegate, $param); - } - } - else - { - //while($row = $recordSet->fetchRow()) - foreach($reader as $row) - { - $obj = $this->applyResultMap($row); - $key = TPropertyAccess::get($obj, $keyProperty); - $map[$key] = ($valueProperty===null) ? $obj : - TPropertyAccess::get($obj, $valueProperty); - } - } - $this->onExecuteQuery($command); - return $map; - } - - /** - * Raises delegate handler. - * This method is invoked for each new list item. It is the responsibility - * of the handler to add the item to the list. - * @param object event parameter - */ - protected function raiseRowDelegate($handler, $param) - { - if(is_string($handler)) - { - call_user_func($handler,$this,$param); - } - else if(is_callable($handler,true)) - { - // an array: 0 - object, 1 - method name/path - list($object,$method)=$handler; - if(is_string($object)) // static method call - call_user_func($handler,$this,$param); - else - { - if(($pos=strrpos($method,'.'))!==false) - { - $object=$this->getSubProperty(substr($method,0,$pos)); - $method=substr($method,$pos+1); - } - $object->$method($this,$param); - } - } - else - throw new TInvalidDataValueException('sqlmap_invalid_delegate', $this->getID(), $handler); - } - - /** - * Executes an SQL statement that returns a single row as an object of the - * type of the $result passed in as a parameter. - * @param IDbConnection database connection - * @param mixed The parameter data (object, arrary, primitive) used to set the parameters in the SQL - * @param mixed The result object. - * @return ${return} - */ - public function executeQueryForObject($connection, $parameter, $result=null) - { - $sql = $this->_command->create($this->_manager, $connection, $this->_statement, $parameter); - return $this->runQueryForObject($connection, $sql, $result); - } - - /** - * Executes an SQL statement that returns a single row as an object of the - * type of the $result passed in as a parameter. - * - * This method should only be called by internal developers, consider using - * executeQueryForObject() first. - * - * @param IDbConnection database connection - * @param array SQL string and subsititution parameters. - * @param object The result object. - * @return object the object. - * @see executeQueryForObject() - */ - public function runQueryForObject($connection, $command, &$result) - { - $object = null; - $connection->setActive(true); - foreach($command->query() as $row) - $object = $this->applyResultMap($row, $result); - - if(!$this->_groupBy->isEmpty()) - { - $list = $this->_groupBy->collect(); - $this->initialGroupByResults(); - $object = $list[0]; - } - - $this->executePostSelect($connection); - $this->onExecuteQuery($command); - - return $object; - } - - /** - * Execute an insert statement. Fill the parameter object with the ouput - * parameters if any, also could return the insert generated key. - * @param IDbConnection database connection - * @param mixed The parameter object used to fill the statement. - * @return string the insert generated key. - */ - public function executeInsert($connection, $parameter) - { - $generatedKey = $this->getPreGeneratedSelectKey($connection, $parameter); - - $command = $this->_command->create($this->_manager, $connection, $this->_statement, $parameter); -// var_dump($command,$parameter); - $result = $command->execute(); - - if($generatedKey===null) - $generatedKey = $this->getPostGeneratedSelectKey($connection, $parameter); - - $this->executePostSelect($connection); - $this->onExecuteQuery($command); - return $generatedKey; - } - - /** - * Gets the insert generated ID before executing an insert statement. - * @param IDbConnection database connection - * @param mixed insert statement parameter. - * @return string new insert ID if pre-select key statement was executed, null otherwise. - */ - protected function getPreGeneratedSelectKey($connection, $parameter) - { - if($this->_statement instanceof TSqlMapInsert) - { - $selectKey = $this->_statement->getSelectKey(); - if(($selectKey!==null) && !$selectKey->getIsAfter()) - return $this->executeSelectKey($connection, $parameter, $selectKey); - } - } - - /** - * Gets the inserted row ID after executing an insert statement. - * @param IDbConnection database connection - * @param mixed insert statement parameter. - * @return string last insert ID, null otherwise. - */ - protected function getPostGeneratedSelectKey($connection, $parameter) - { - if($this->_statement instanceof TSqlMapInsert) - { - $selectKey = $this->_statement->getSelectKey(); - if(($selectKey!==null) && $selectKey->getIsAfter()) - return $this->executeSelectKey($connection, $parameter, $selectKey); - } - } - - /** - * Execute the select key statement, used to obtain last insert ID. - * @param IDbConnection database connection - * @param mixed insert statement parameter - * @param TSqlMapSelectKey select key statement - * @return string last insert ID. - */ - protected function executeSelectKey($connection, $parameter, $selectKey) - { - $mappedStatement = $this->getManager()->getMappedStatement($selectKey->getID()); - $generatedKey = $mappedStatement->executeQueryForObject( - $connection, $parameter, null); - if(strlen($prop = $selectKey->getProperty()) > 0) - TPropertyAccess::set($parameter, $prop, $generatedKey); - return $generatedKey; - } - - /** - * Execute an update statement. Also used for delete statement. - * Return the number of rows effected. - * @param IDbConnection database connection - * @param mixed The object used to set the parameters in the SQL. - * @return integer The number of rows effected. - */ - public function executeUpdate($connection, $parameter) - { - $sql = $this->_command->create($this->getManager(),$connection, $this->_statement, $parameter); - $affectedRows = $sql->execute(); - //$this->executeSQLQuery($connection, $sql); - $this->executePostSelect($connection); - $this->onExecuteQuery($sql); - return $affectedRows; - } - - /** - * Process 'select' result properties - * @param IDbConnection database connection - */ - protected function executePostSelect($connection) - { - while(count($this->_selectQueue)) - { - $postSelect = array_shift($this->_selectQueue); - $method = $postSelect->getMethod(); - $statement = $postSelect->getStatement(); - $property = $postSelect->getResultProperty()->getProperty(); - $keys = $postSelect->getKeys(); - $resultObject = $postSelect->getResultObject(); - - if($method == self::QUERY_FOR_LIST || $method == self::QUERY_FOR_ARRAY) - { - $values = $statement->executeQueryForList($connection, $keys, null); - - if($method == self::QUERY_FOR_ARRAY) - $values = $values->toArray(); - TPropertyAccess::set($resultObject, $property, $values); - } - else if($method == self::QUERY_FOR_OBJECT) - { - $value = $statement->executeQueryForObject($connection, $keys, null); - TPropertyAccess::set($resultObject, $property, $value); - } - } - } - - /** - * Raise the execute query event. - * @param array prepared SQL statement and subsititution parameters - */ - public function onExecuteQuery($sql) - { - $this->raiseEvent('OnExecuteQuery', $this, $sql); - } - - /** - * Apply result mapping. - * @param array a result set row retrieved from the database - * @param object the result object, will create if necessary. - * @return object the result filled with data, null if not filled. - */ - protected function applyResultMap($row, &$resultObject=null) - { - if($row === false) return null; - - $resultMapName = $this->_statement->getResultMap(); - $resultClass = $this->_statement->getResultClass(); - - $obj=null; - if($this->getManager()->getResultMaps()->contains($resultMapName)) - $obj = $this->fillResultMap($resultMapName, $row, null, $resultObject); - else if(strlen($resultClass) > 0) - $obj = $this->fillResultClass($resultClass, $row, $resultObject); - else - $obj = $this->fillDefaultResultMap(null, $row, $resultObject); - if(class_exists('TActiveRecord',false) && $obj instanceof TActiveRecord) - //Create a new clean active record. - $obj=TActiveRecord::createRecord(get_class($obj),$obj); - return $obj; - } - - /** - * Fill the result using ResultClass, will creates new result object if required. - * @param string result object class name - * @param array a result set row retrieved from the database - * @param object the result object, will create if necessary. - * @return object result object filled with data - */ - protected function fillResultClass($resultClass, $row, $resultObject) - { - if($resultObject===null) - { - $registry = $this->getManager()->getTypeHandlers(); - $resultObject = $this->_statement->createInstanceOfResultClass($registry,$row); - } - - if($resultObject instanceOf ArrayAccess) - return $this->fillResultArrayList($row, $resultObject); - else if(is_object($resultObject)) - return $this->fillResultObjectProperty($row, $resultObject); - else - return $this->fillDefaultResultMap(null, $row, $resultObject); - } - - /** - * Apply the result to a TList or an array. - * @param array a result set row retrieved from the database - * @param object result object, array or list - * @return object result filled with data. - */ - protected function fillResultArrayList($row, $resultObject) - { - if($resultObject instanceof TList) - foreach($row as $v) - $resultObject[] = $v; - else - foreach($row as $k => $v) - $resultObject[$k] = $v; - return $resultObject; - } - - /** - * Apply the result to an object. - * @param array a result set row retrieved from the database - * @param object result object, array or list - * @return object result filled with data. - */ - protected function fillResultObjectProperty($row, $resultObject) - { - $index = 0; - $registry=$this->getManager()->getTypeHandlers(); - foreach($row as $k=>$v) - { - $property = new TResultProperty; - if(is_string($k) && strlen($k) > 0) - $property->setColumn($k); - $property->setColumnIndex(++$index); - $type = gettype(TPropertyAccess::get($resultObject,$k)); - $property->setType($type); - $value = $property->getPropertyValue($registry,$row); - TPropertyAccess::set($resultObject, $k,$value); - } - return $resultObject; - } - - /** - * Fills the result object according to result mappings. - * @param string result map name. - * @param array a result set row retrieved from the database - * @param object result object to fill, will create new instances if required. - * @return object result object filled with data. - */ - protected function fillResultMap($resultMapName, $row, $parentGroup=null, &$resultObject=null) - { - $resultMap = $this->getManager()->getResultMap($resultMapName); - $registry = $this->getManager()->getTypeHandlers(); - $resultMap = $resultMap->resolveSubMap($registry,$row); - - if($resultObject===null) - $resultObject = $resultMap->createInstanceOfResult($registry); - - if(is_object($resultObject)) - { - if(strlen($resultMap->getGroupBy()) > 0) - return $this->addResultMapGroupBy($resultMap, $row, $parentGroup, $resultObject); - else - foreach($resultMap->getColumns() as $property) - $this->setObjectProperty($resultMap, $property, $row, $resultObject); - } - else - { - $resultObject = $this->fillDefaultResultMap($resultMap, $row, $resultObject); - } - return $resultObject; - } - - /** - * ResultMap with GroupBy property. Save object collection graph in a tree - * and collect the result later. - * @param TResultMap result mapping details. - * @param array a result set row retrieved from the database - * @param object the result object - * @return object result object. - */ - protected function addResultMapGroupBy($resultMap, $row, $parent, &$resultObject) - { - $group = $this->getResultMapGroupKey($resultMap, $row); - - if(empty($parent)) - { - $rootObject = array('object'=>$resultObject, 'property' => null); - $this->_groupBy->add(null, $group, $rootObject); - } - - foreach($resultMap->getColumns() as $property) - { - //set properties. - $this->setObjectProperty($resultMap, $property, $row, $resultObject); - $nested = $property->getResultMapping(); - - //nested property - if($this->getManager()->getResultMaps()->contains($nested)) - { - $nestedMap = $this->getManager()->getResultMap($nested); - $groupKey = $this->getResultMapGroupKey($nestedMap, $row); - - //add the node reference first - if(empty($parent)) - $this->_groupBy->add($group, $groupKey, ''); - - //get the nested result mapping value - $value = $this->fillResultMap($nested, $row, $groupKey); - - //add it to the object tree graph - $groupObject = array('object'=>$value, 'property' => $property->getProperty()); - if(empty($parent)) - $this->_groupBy->add($group, $groupKey, $groupObject); - else - $this->_groupBy->add($parent, $groupKey, $groupObject); - } - } - return $resultObject; - } - - /** - * Gets the result 'group by' groupping key for each row. - * @param TResultMap result mapping details. - * @param array a result set row retrieved from the database - * @return string groupping key. - */ - protected function getResultMapGroupKey($resultMap, $row) - { - $groupBy = $resultMap->getGroupBy(); - if(isset($row[$groupBy])) - return $resultMap->getID().$row[$groupBy]; - else - return $resultMap->getID().crc32(serialize($row)); - } - - /** - * Fill the result map using default settings. If $resultMap is null - * the result object returned will be guessed from $resultObject. - * @param TResultMap result mapping details. - * @param array a result set row retrieved from the database - * @param object the result object - * @return mixed the result object filled with data. - */ - protected function fillDefaultResultMap($resultMap, $row, $resultObject) - { - if($resultObject===null) - $resultObject=''; - - if($resultMap!==null) - $result = $this->fillArrayResultMap($resultMap, $row, $resultObject); - else - $result = $row; - - //if scalar result types - if(count($result) == 1 && ($type = gettype($resultObject))!= 'array') - return $this->getScalarResult($result, $type); - else - return $result; - } - - /** - * Retrieve the result map as an array. - * @param TResultMap result mapping details. - * @param array a result set row retrieved from the database - * @param object the result object - * @return array array list of result objects. - */ - protected function fillArrayResultMap($resultMap, $row, $resultObject) - { - $result = array(); - $registry=$this->getManager()->getTypeHandlers(); - foreach($resultMap->getColumns() as $column) - { - if(($column->getType()===null) - && ($resultObject!==null) && !is_object($resultObject)) - $column->setType(gettype($resultObject)); - $result[$column->getProperty()] = $column->getPropertyValue($registry,$row); - } - return $result; - } - - /** - * Converts the first array value to scalar value of given type. - * @param array list of results - * @param string scalar type. - * @return mixed scalar value. - */ - protected function getScalarResult($result, $type) - { - $scalar = array_shift($result); - settype($scalar, $type); - return $scalar; - } - - /** - * Set a property of the result object with appropriate value. - * @param TResultMap result mapping details. - * @param TResultProperty the result property to fill. - * @param array a result set row retrieved from the database - * @param object the result object - */ - protected function setObjectProperty($resultMap, $property, $row, &$resultObject) - { - $select = $property->getSelect(); - $key = $property->getProperty(); - $nested = $property->getNestedResultMap(); - $registry=$this->getManager()->getTypeHandlers(); - if($key === '') - { - $resultObject = $property->getPropertyValue($registry,$row); - } - else if(strlen($select) == 0 && ($nested===null)) - { - $value = $property->getPropertyValue($registry,$row); - - $this->_IsRowDataFound = $this->_IsRowDataFound || ($value != null); - if(is_array($resultObject) || is_object($resultObject)) - TPropertyAccess::set($resultObject, $key, $value); - else - $resultObject = $value; - } - else if($nested!==null) - { - if($property->instanceOfListType($resultObject) || $property->instanceOfArrayType($resultObject)) - { - if(strlen($resultMap->getGroupBy()) <= 0) - throw new TSqlMapExecutionException( - 'sqlmap_non_groupby_array_list_type', $resultMap->getID(), - get_class($resultObject), $key); - } - else - { - $obj = $nested->createInstanceOfResult($this->getManager()->getTypeHandlers()); - if($this->fillPropertyWithResultMap($nested, $row, $obj) == false) - $obj = null; - TPropertyAccess::set($resultObject, $key, $obj); - } - } - else //'select' ResultProperty - { - $this->enquequePostSelect($select, $resultMap, $property, $row, $resultObject); - } - } - - /** - * Add nested result property to post select queue. - * @param string post select statement ID - * @param TResultMap current result mapping details. - * @param TResultProperty current result property. - * @param array a result set row retrieved from the database - * @param object the result object - */ - protected function enquequePostSelect($select, $resultMap, $property, $row, $resultObject) - { - $statement = $this->getManager()->getMappedStatement($select); - $key = $this->getPostSelectKeys($resultMap, $property, $row); - $postSelect = new TPostSelectBinding; - $postSelect->setStatement($statement); - $postSelect->setResultObject($resultObject); - $postSelect->setResultProperty($property); - $postSelect->setKeys($key); - - if($property->instanceOfListType($resultObject)) - { - $values = null; - if($property->getLazyLoad()) - { - $values = TLazyLoadList::newInstance($statement, $key, - $resultObject, $property->getProperty()); - TPropertyAccess::set($resultObject, $property->getProperty(), $values); - } - else - $postSelect->setMethod(self::QUERY_FOR_LIST); - } - else if($property->instanceOfArrayType($resultObject)) - $postSelect->setMethod(self::QUERY_FOR_ARRAY); - else - $postSelect->setMethod(self::QUERY_FOR_OBJECT); - - if(!$property->getLazyLoad()) - $this->_selectQueue[] = $postSelect; - } - - /** - * Finds in the post select property the SQL statement primary selection keys. - * @param TResultMap result mapping details - * @param TResultProperty result property - * @param array current row data. - * @return array list of primary key values. - */ - protected function getPostSelectKeys($resultMap, $property,$row) - { - $value = $property->getColumn(); - if(is_int(strpos($value.',',0)) || is_int(strpos($value, '=',0))) - { - $keys = array(); - foreach(explode(',', $value) as $entry) - { - $pair =explode('=',$entry); - $keys[trim($pair[0])] = $row[trim($pair[1])]; - } - return $keys; - } - else - { - $registry=$this->getManager()->getTypeHandlers(); - return $property->getPropertyValue($registry,$row); - } - } - - /** - * Fills the property with result mapping results. - * @param TResultMap nested result mapping details. - * @param array a result set row retrieved from the database - * @param object the result object - * @return boolean true if the data was found, false otherwise. - */ - protected function fillPropertyWithResultMap($resultMap, $row, &$resultObject) - { - $dataFound = false; - foreach($resultMap->getColumns() as $property) - { - $this->_IsRowDataFound = false; - $this->setObjectProperty($resultMap, $property, $row, $resultObject); - $dataFound = $dataFound || $this->_IsRowDataFound; - } - $this->_IsRowDataFound = $dataFound; - return $dataFound; - } - - public function __wakeup() - { - parent::__wakeup(); - if (is_null($this->_selectQueue)) $this->_selectQueue = array(); - } - - public function __sleep() - { - $exprops = array(); $cn = __CLASS__; - if (!count($this->_selectQueue)) $exprops[] = "\0$cn\0_selectQueue"; - if (is_null($this->_groupBy)) $exprops[] = "\0$cn\0_groupBy"; - if (!$this->_IsRowDataFound) $exprops[] = "\0$cn\0_IsRowDataFound"; - return array_diff(parent::__sleep(),$exprops); - } -} - -/** - * TPostSelectBinding class. - * - * @author Wei Zhuo - * @version $Id$ - * @package System.Data.SqlMap.Statements - * @since 3.1 - */ -class TPostSelectBinding -{ - private $_statement=null; - private $_property=null; - private $_resultObject=null; - private $_keys=null; - private $_method=TMappedStatement::QUERY_FOR_LIST; - - public function getStatement(){ return $this->_statement; } - public function setStatement($value){ $this->_statement = $value; } - - public function getResultProperty(){ return $this->_property; } - public function setResultProperty($value){ $this->_property = $value; } - - public function getResultObject(){ return $this->_resultObject; } - public function setResultObject($value){ $this->_resultObject = $value; } - - public function getKeys(){ return $this->_keys; } - public function setKeys($value){ $this->_keys = $value; } - - public function getMethod(){ return $this->_method; } - public function setMethod($value){ $this->_method = $value; } -} - -/** - * TSQLMapObjectCollectionTree class. - * - * Maps object collection graphs as trees. Nodes in the collection can - * be {@link add} using parent relationships. The object collections can be - * build using the {@link collect} method. - * - * @author Wei Zhuo - * @version $Id$ - * @package System.Data.SqlMap.Statements - * @since 3.1 - */ -class TSqlMapObjectCollectionTree extends TComponent -{ - /** - * @var array object graph as tree - */ - private $_tree = array(); - /** - * @var array tree node values - */ - private $_entries = array(); - /** - * @var array resulting object collection - */ - private $_list = array(); - - /** - * @return boolean true if the graph is empty - */ - public function isEmpty() - { - return count($this->_entries) == 0; - } - - /** - * Add a new node to the object tree graph. - * @param string parent node id - * @param string new node id - * @param mixed node value - */ - public function add($parent, $node, $object='') - { - if(isset($this->_entries[$parent]) && ($this->_entries[$parent]!==null) - && isset($this->_entries[$node]) && ($this->_entries[$node]!==null)) - { - $this->_entries[$node] = $object; - return; - } - $this->_entries[$node] = $object; - if(empty($parent)) - { - if(isset($this->_entries[$node])) - return; - $this->_tree[$node] = array(); - } - $found = $this->addNode($this->_tree, $parent, $node); - if(!$found && !empty($parent)) - { - $this->_tree[$parent] = array(); - if(!isset($this->_entries[$parent]) || $object !== '') - $this->_entries[$parent] = $object; - $this->addNode($this->_tree, $parent, $node); - } - } - - /** - * Find the parent node and add the new node as its child. - * @param array list of nodes to check - * @param string parent node id - * @param string new node id - * @return boolean true if parent node is found. - */ - protected function addNode(&$childs, $parent, $node) - { - $found = false; - reset($childs); - for($i = 0, $k = count($childs); $i < $k; $i++) - { - $key = key($childs); - next($childs); - if($key == $parent) - { - $found = true; - $childs[$key][$node] = array(); - } - else - { - $found = $found || $this->addNode($childs[$key], $parent, $node); - } - } - return $found; - } - - /** - * @return array object collection - */ - public function collect() - { - while(count($this->_tree) > 0) - $this->collectChildren(null, $this->_tree); - return $this->getCollection(); - } - - /** - * @param array list of nodes to check - * @return boolean true if all nodes are leaf nodes, false otherwise - */ - protected function hasChildren(&$nodes) - { - $hasChildren = false; - foreach($nodes as $node) - if(count($node) != 0) - return true; - return $hasChildren; - } - - /** - * Visit all the child nodes and collect them by removing. - * @param string parent node id - * @param array list of child nodes. - */ - protected function collectChildren($parent, &$nodes) - { - $noChildren = !$this->hasChildren($nodes); - $childs = array(); - for(reset($nodes); $key = key($nodes);) - { - next($nodes); - if($noChildren) - { - $childs[] = $key; - unset($nodes[$key]); - } - else - $this->collectChildren($key, $nodes[$key]); - } - if(count($childs) > 0) - $this->onChildNodesVisited($parent, $childs); - } - - /** - * Set the object properties for all the child nodes visited. - * @param string parent node id - * @param array list of child nodes visited. - */ - protected function onChildNodesVisited($parent, $nodes) - { - if(empty($parent) || empty($this->_entries[$parent])) - return; - - $parentObject = $this->_entries[$parent]['object']; - $property = $this->_entries[$nodes[0]]['property']; - - $list = TPropertyAccess::get($parentObject, $property); - - foreach($nodes as $node) - { - if($list instanceof TList) - $parentObject->{$property}[] = $this->_entries[$node]['object']; - else if(is_array($list)) - $list[] = $this->_entries[$node]['object']; - else - throw new TSqlMapExecutionException( - 'sqlmap_property_must_be_list'); - } - - if(is_array($list)) - TPropertyAccess::set($parentObject, $property, $list); - - if($this->_entries[$parent]['property'] === null) - $this->_list[] = $parentObject; - } - - /** - * @return array object collection. - */ - protected function getCollection() - { - return $this->_list; - } - - public function __sleep() - { - $exprops = array(); $cn = __CLASS__; - if (!count($this->_tree)) $exprops[] = "\0$cn\0_tree"; - if (!count($this->_entries)) $exprops[] = "\0$cn\0_entries"; - if (!count($this->_list)) $exprops[] = "\0$cn\0_list"; - return array_diff(parent::__sleep(),$exprops); - } -} - -/** - * TResultSetListItemParameter class - * - * @author Wei Zhuo - * @version $Id$ - * @package System.Data.SqlMap.Statements - * @since 3.1 - */ -class TResultSetListItemParameter extends TComponent -{ - private $_resultObject; - private $_parameterObject; - private $_list; - - public function __construct($result, $parameter, &$list) - { - $this->_resultObject = $result; - $this->_parameterObject = $parameter; - $this->_list = &$list; - } - - public function getResult() - { - return $this->_resultObject; - } - - public function getParameter() - { - return $this->_parameterObject; - } - - public function &getList() - { - return $this->_list; - } -} - -/** - * TResultSetMapItemParameter class. - * - * @author Wei Zhuo - * @version $Id$ - * @package System.Data.SqlMap.Statements - * @since 3.1 - */ -class TResultSetMapItemParameter extends TComponent -{ - private $_key; - private $_value; - private $_parameterObject; - private $_map; - - public function __construct($key, $value, $parameter, &$map) - { - $this->_key = $key; - $this->_value = $value; - $this->_parameterObject = $parameter; - $this->_map = &$map; - } - - public function getKey() - { - return $this->_key; - } - - public function getValue() - { - return $this->_value; - } - - public function getParameter() - { - return $this->_parameterObject; - } - - public function &getMap() - { - return $this->_map; - } -} - + + * @link http://www.pradosoft.com/ + * @copyright Copyright © 2005-2012 PradoSoft + * @license http://www.pradosoft.com/license/ + * @version $Id$ + * @package System.Data.SqlMap.Statements + */ + +/** + * TMappedStatement class executes SQL mapped statements. Mapped Statements can + * hold any SQL statement and use Parameter Maps and Result Maps for input and output. + * + * This class is usualy instantiated during SQLMap configuration by TSqlDomBuilder. + * + * @author Wei Zhuo + * @version $Id$ + * @package System.Data.SqlMap.Statements + * @since 3.0 + */ +class TMappedStatement extends TComponent implements IMappedStatement +{ + /** + * @var TSqlMapStatement current SQL statement. + */ + private $_statement; + + /** + * @var TPreparedCommand SQL command prepareer + */ + private $_command; + + /** + * @var TSqlMapper sqlmap used by this mapper. + */ + private $_manager; + + /** + * @var TPostSelectBinding[] post select statement queue. + */ + private $_selectQueue=array(); + + /** + * @var boolean true when data is mapped to a particular row. + */ + private $_IsRowDataFound = false; + + /** + * @var TSQLMapObjectCollectionTree group by object collection tree + */ + private $_groupBy; + + /** + * @var Post select is to query for list. + */ + const QUERY_FOR_LIST = 0; + + /** + * @var Post select is to query for list. + */ + const QUERY_FOR_ARRAY = 1; + + /** + * @var Post select is to query for object. + */ + const QUERY_FOR_OBJECT = 2; + + /** + * @return string Name used to identify the TMappedStatement amongst the others. + * This the name of the SQL statement by default. + */ + public function getID() + { + return $this->_statement->ID; + } + + /** + * @return TSqlMapStatement The SQL statment used by this MappedStatement + */ + public function getStatement() + { + return $this->_statement; + } + + /** + * @return TSqlMapper The SqlMap used by this MappedStatement + */ + public function getManager() + { + return $this->_manager; + } + + /** + * @return TPreparedCommand command to prepare SQL statements. + */ + public function getCommand() + { + return $this->_command; + } + + /** + * Empty the group by results cache. + */ + protected function initialGroupByResults() + { + $this->_groupBy = new TSqlMapObjectCollectionTree(); + } + + /** + * Creates a new mapped statement. + * @param TSqlMapper an sqlmap. + * @param TSqlMapStatement An SQL statement. + */ + public function __construct(TSqlMapManager $sqlMap, TSqlMapStatement $statement) + { + $this->_manager = $sqlMap; + $this->_statement = $statement; + $this->_command = new TPreparedCommand(); + $this->initialGroupByResults(); + } + + public function getSqlString() + { + return $this->getStatement()->getSqlText()->getPreparedStatement()->getPreparedSql(); + } + + /** + * Execute SQL Query. + * @param IDbConnection database connection + * @param array SQL statement and parameters. + * @return mixed record set if applicable. + * @throws TSqlMapExecutionException if execution error or false record set. + * @throws TSqlMapQueryExecutionException if any execution error + */ +/* protected function executeSQLQuery($connection, $sql) + { + try + { + if(!($recordSet = $connection->execute($sql['sql'],$sql['parameters']))) + { + throw new TSqlMapExecutionException( + 'sqlmap_execution_error_no_record', $this->getID(), + $connection->ErrorMsg()); + } + return $recordSet; + } + catch (Exception $e) + { + throw new TSqlMapQueryExecutionException($this->getStatement(), $e); + } + }*/ + + /** + * Execute SQL Query with limits. + * @param IDbConnection database connection + * @param array SQL statement and parameters. + * @return mixed record set if applicable. + * @throws TSqlMapExecutionException if execution error or false record set. + * @throws TSqlMapQueryExecutionException if any execution error + */ + protected function executeSQLQueryLimit($connection, $command, $max, $skip) + { + if($max>-1 || $skip > -1) + { + $maxStr=$max>0?' LIMIT '.$max:''; + $skipStr=$skip>0?' OFFSET '.$skip:''; + $command->setText($command->getText().$maxStr.$skipStr); + } + $connection->setActive(true); + return $command->query(); + + /*//var_dump($command); + try + { + $recordSet = $connection->selectLimit($sql['sql'],$max,$skip,$sql['parameters']); + if(!$recordSet) + { + throw new TSqlMapExecutionException( + 'sqlmap_execution_error_query_for_list', + $connection->ErrorMsg()); + } + return $recordSet; + } + catch (Exception $e) + { + throw new TSqlMapQueryExecutionException($this->getStatement(), $e); + }*/ + } + + /** + * Executes the SQL and retuns a List of result objects. + * @param IDbConnection database connection + * @param mixed The object used to set the parameters in the SQL. + * @param object result collection object. + * @param integer The number of rows to skip over. + * @param integer The maximum number of rows to return. + * @return array a list of result objects + * @param callback row delegate handler + * @see executeQueryForList() + */ + public function executeQueryForList($connection, $parameter, $result=null, $skip=-1, $max=-1, $delegate=null) + { + $sql = $this->_command->create($this->_manager, $connection, $this->_statement, $parameter,$skip,$max); + return $this->runQueryForList($connection, $parameter, $sql, $result, $delegate); + } + + /** + * Executes the SQL and retuns a List of result objects. + * + * This method should only be called by internal developers, consider using + * executeQueryForList() first. + * + * @param IDbConnection database connection + * @param mixed The object used to set the parameters in the SQL. + * @param array SQL string and subsititution parameters. + * @param object result collection object. + * @param integer The number of rows to skip over. + * @param integer The maximum number of rows to return. + * @param callback row delegate handler + * @return array a list of result objects + * @see executeQueryForList() + */ + public function runQueryForList($connection, $parameter, $sql, $result, $delegate=null) + { + $registry=$this->getManager()->getTypeHandlers(); + $list = $result instanceof ArrayAccess ? $result : + $this->_statement->createInstanceOfListClass($registry); + $connection->setActive(true); + $reader = $sql->query(); + //$reader = $this->executeSQLQueryLimit($connection, $sql, $max, $skip); + if($delegate!==null) + { + foreach($reader as $row) + { + $obj = $this->applyResultMap($row); + $param = new TResultSetListItemParameter($obj, $parameter, $list); + $this->raiseRowDelegate($delegate, $param); + } + } + else + { + //var_dump($sql,$parameter); + foreach($reader as $row) + { +// var_dump($row); + $list[] = $this->applyResultMap($row); + } + } + + if(!$this->_groupBy->isEmpty()) + { + $list = $this->_groupBy->collect(); + $this->initialGroupByResults(); + } + + $this->executePostSelect($connection); + $this->onExecuteQuery($sql); + + return $list; + } + + /** + * Executes the SQL and retuns all rows selected in a map that is keyed on + * the property named in the keyProperty parameter. The value at each key + * will be the value of the property specified in the valueProperty parameter. + * If valueProperty is null, the entire result object will be entered. + * @param IDbConnection database connection + * @param mixed The object used to set the parameters in the SQL. + * @param string The property of the result object to be used as the key. + * @param string The property of the result object to be used as the value (or null). + * @param callback row delegate handler + * @return array An array of object containing the rows keyed by keyProperty. + */ + public function executeQueryForMap($connection, $parameter, $keyProperty, $valueProperty=null, $skip=-1, $max=-1, $delegate=null) + { + $sql = $this->_command->create($this->_manager, $connection, $this->_statement, $parameter, $skip, $max); + return $this->runQueryForMap($connection, $parameter, $sql, $keyProperty, $valueProperty, $delegate); + } + + /** + * Executes the SQL and retuns all rows selected in a map that is keyed on + * the property named in the keyProperty parameter. The value at each key + * will be the value of the property specified in the valueProperty parameter. + * If valueProperty is null, the entire result object will be entered. + * + * This method should only be called by internal developers, consider using + * executeQueryForMap() first. + * + * @param IDbConnection database connection + * @param mixed The object used to set the parameters in the SQL. + * @param array SQL string and subsititution parameters. + * @param string The property of the result object to be used as the key. + * @param string The property of the result object to be used as the value (or null). + * @param callback row delegate, a callback function + * @return array An array of object containing the rows keyed by keyProperty. + * @see executeQueryForMap() + */ + public function runQueryForMap($connection, $parameter, $command, $keyProperty, $valueProperty=null, $delegate=null) + { + $map = array(); + //$recordSet = $this->executeSQLQuery($connection, $sql); + $connection->setActive(true); + $reader = $command->query(); + if($delegate!==null) + { + //while($row = $recordSet->fetchRow()) + foreach($reader as $row) + { + $obj = $this->applyResultMap($row); + $key = TPropertyAccess::get($obj, $keyProperty); + $value = ($valueProperty===null) ? $obj : + TPropertyAccess::get($obj, $valueProperty); + $param = new TResultSetMapItemParameter($key, $value, $parameter, $map); + $this->raiseRowDelegate($delegate, $param); + } + } + else + { + //while($row = $recordSet->fetchRow()) + foreach($reader as $row) + { + $obj = $this->applyResultMap($row); + $key = TPropertyAccess::get($obj, $keyProperty); + $map[$key] = ($valueProperty===null) ? $obj : + TPropertyAccess::get($obj, $valueProperty); + } + } + $this->onExecuteQuery($command); + return $map; + } + + /** + * Raises delegate handler. + * This method is invoked for each new list item. It is the responsibility + * of the handler to add the item to the list. + * @param object event parameter + */ + protected function raiseRowDelegate($handler, $param) + { + if(is_string($handler)) + { + call_user_func($handler,$this,$param); + } + else if(is_callable($handler,true)) + { + // an array: 0 - object, 1 - method name/path + list($object,$method)=$handler; + if(is_string($object)) // static method call + call_user_func($handler,$this,$param); + else + { + if(($pos=strrpos($method,'.'))!==false) + { + $object=$this->getSubProperty(substr($method,0,$pos)); + $method=substr($method,$pos+1); + } + $object->$method($this,$param); + } + } + else + throw new TInvalidDataValueException('sqlmap_invalid_delegate', $this->getID(), $handler); + } + + /** + * Executes an SQL statement that returns a single row as an object of the + * type of the $result passed in as a parameter. + * @param IDbConnection database connection + * @param mixed The parameter data (object, arrary, primitive) used to set the parameters in the SQL + * @param mixed The result object. + * @return ${return} + */ + public function executeQueryForObject($connection, $parameter, $result=null) + { + $sql = $this->_command->create($this->_manager, $connection, $this->_statement, $parameter); + return $this->runQueryForObject($connection, $sql, $result); + } + + /** + * Executes an SQL statement that returns a single row as an object of the + * type of the $result passed in as a parameter. + * + * This method should only be called by internal developers, consider using + * executeQueryForObject() first. + * + * @param IDbConnection database connection + * @param array SQL string and subsititution parameters. + * @param object The result object. + * @return object the object. + * @see executeQueryForObject() + */ + public function runQueryForObject($connection, $command, &$result) + { + $object = null; + $connection->setActive(true); + foreach($command->query() as $row) + $object = $this->applyResultMap($row, $result); + + if(!$this->_groupBy->isEmpty()) + { + $list = $this->_groupBy->collect(); + $this->initialGroupByResults(); + $object = $list[0]; + } + + $this->executePostSelect($connection); + $this->onExecuteQuery($command); + + return $object; + } + + /** + * Execute an insert statement. Fill the parameter object with the ouput + * parameters if any, also could return the insert generated key. + * @param IDbConnection database connection + * @param mixed The parameter object used to fill the statement. + * @return string the insert generated key. + */ + public function executeInsert($connection, $parameter) + { + $generatedKey = $this->getPreGeneratedSelectKey($connection, $parameter); + + $command = $this->_command->create($this->_manager, $connection, $this->_statement, $parameter); +// var_dump($command,$parameter); + $result = $command->execute(); + + if($generatedKey===null) + $generatedKey = $this->getPostGeneratedSelectKey($connection, $parameter); + + $this->executePostSelect($connection); + $this->onExecuteQuery($command); + return $generatedKey; + } + + /** + * Gets the insert generated ID before executing an insert statement. + * @param IDbConnection database connection + * @param mixed insert statement parameter. + * @return string new insert ID if pre-select key statement was executed, null otherwise. + */ + protected function getPreGeneratedSelectKey($connection, $parameter) + { + if($this->_statement instanceof TSqlMapInsert) + { + $selectKey = $this->_statement->getSelectKey(); + if(($selectKey!==null) && !$selectKey->getIsAfter()) + return $this->executeSelectKey($connection, $parameter, $selectKey); + } + } + + /** + * Gets the inserted row ID after executing an insert statement. + * @param IDbConnection database connection + * @param mixed insert statement parameter. + * @return string last insert ID, null otherwise. + */ + protected function getPostGeneratedSelectKey($connection, $parameter) + { + if($this->_statement instanceof TSqlMapInsert) + { + $selectKey = $this->_statement->getSelectKey(); + if(($selectKey!==null) && $selectKey->getIsAfter()) + return $this->executeSelectKey($connection, $parameter, $selectKey); + } + } + + /** + * Execute the select key statement, used to obtain last insert ID. + * @param IDbConnection database connection + * @param mixed insert statement parameter + * @param TSqlMapSelectKey select key statement + * @return string last insert ID. + */ + protected function executeSelectKey($connection, $parameter, $selectKey) + { + $mappedStatement = $this->getManager()->getMappedStatement($selectKey->getID()); + $generatedKey = $mappedStatement->executeQueryForObject( + $connection, $parameter, null); + if(strlen($prop = $selectKey->getProperty()) > 0) + TPropertyAccess::set($parameter, $prop, $generatedKey); + return $generatedKey; + } + + /** + * Execute an update statement. Also used for delete statement. + * Return the number of rows effected. + * @param IDbConnection database connection + * @param mixed The object used to set the parameters in the SQL. + * @return integer The number of rows effected. + */ + public function executeUpdate($connection, $parameter) + { + $sql = $this->_command->create($this->getManager(),$connection, $this->_statement, $parameter); + $affectedRows = $sql->execute(); + //$this->executeSQLQuery($connection, $sql); + $this->executePostSelect($connection); + $this->onExecuteQuery($sql); + return $affectedRows; + } + + /** + * Process 'select' result properties + * @param IDbConnection database connection + */ + protected function executePostSelect($connection) + { + while(count($this->_selectQueue)) + { + $postSelect = array_shift($this->_selectQueue); + $method = $postSelect->getMethod(); + $statement = $postSelect->getStatement(); + $property = $postSelect->getResultProperty()->getProperty(); + $keys = $postSelect->getKeys(); + $resultObject = $postSelect->getResultObject(); + + if($method == self::QUERY_FOR_LIST || $method == self::QUERY_FOR_ARRAY) + { + $values = $statement->executeQueryForList($connection, $keys, null); + + if($method == self::QUERY_FOR_ARRAY) + $values = $values->toArray(); + TPropertyAccess::set($resultObject, $property, $values); + } + else if($method == self::QUERY_FOR_OBJECT) + { + $value = $statement->executeQueryForObject($connection, $keys, null); + TPropertyAccess::set($resultObject, $property, $value); + } + } + } + + /** + * Raise the execute query event. + * @param array prepared SQL statement and subsititution parameters + */ + public function onExecuteQuery($sql) + { + $this->raiseEvent('OnExecuteQuery', $this, $sql); + } + + /** + * Apply result mapping. + * @param array a result set row retrieved from the database + * @param object the result object, will create if necessary. + * @return object the result filled with data, null if not filled. + */ + protected function applyResultMap($row, &$resultObject=null) + { + if($row === false) return null; + + $resultMapName = $this->_statement->getResultMap(); + $resultClass = $this->_statement->getResultClass(); + + $obj=null; + if($this->getManager()->getResultMaps()->contains($resultMapName)) + $obj = $this->fillResultMap($resultMapName, $row, null, $resultObject); + else if(strlen($resultClass) > 0) + $obj = $this->fillResultClass($resultClass, $row, $resultObject); + else + $obj = $this->fillDefaultResultMap(null, $row, $resultObject); + if(class_exists('TActiveRecord',false) && $obj instanceof TActiveRecord) + //Create a new clean active record. + $obj=TActiveRecord::createRecord(get_class($obj),$obj); + return $obj; + } + + /** + * Fill the result using ResultClass, will creates new result object if required. + * @param string result object class name + * @param array a result set row retrieved from the database + * @param object the result object, will create if necessary. + * @return object result object filled with data + */ + protected function fillResultClass($resultClass, $row, $resultObject) + { + if($resultObject===null) + { + $registry = $this->getManager()->getTypeHandlers(); + $resultObject = $this->_statement->createInstanceOfResultClass($registry,$row); + } + + if($resultObject instanceOf ArrayAccess) + return $this->fillResultArrayList($row, $resultObject); + else if(is_object($resultObject)) + return $this->fillResultObjectProperty($row, $resultObject); + else + return $this->fillDefaultResultMap(null, $row, $resultObject); + } + + /** + * Apply the result to a TList or an array. + * @param array a result set row retrieved from the database + * @param object result object, array or list + * @return object result filled with data. + */ + protected function fillResultArrayList($row, $resultObject) + { + if($resultObject instanceof TList) + foreach($row as $v) + $resultObject[] = $v; + else + foreach($row as $k => $v) + $resultObject[$k] = $v; + return $resultObject; + } + + /** + * Apply the result to an object. + * @param array a result set row retrieved from the database + * @param object result object, array or list + * @return object result filled with data. + */ + protected function fillResultObjectProperty($row, $resultObject) + { + $index = 0; + $registry=$this->getManager()->getTypeHandlers(); + foreach($row as $k=>$v) + { + $property = new TResultProperty; + if(is_string($k) && strlen($k) > 0) + $property->setColumn($k); + $property->setColumnIndex(++$index); + $type = gettype(TPropertyAccess::get($resultObject,$k)); + $property->setType($type); + $value = $property->getPropertyValue($registry,$row); + TPropertyAccess::set($resultObject, $k,$value); + } + return $resultObject; + } + + /** + * Fills the result object according to result mappings. + * @param string result map name. + * @param array a result set row retrieved from the database + * @param object result object to fill, will create new instances if required. + * @return object result object filled with data. + */ + protected function fillResultMap($resultMapName, $row, $parentGroup=null, &$resultObject=null) + { + $resultMap = $this->getManager()->getResultMap($resultMapName); + $registry = $this->getManager()->getTypeHandlers(); + $resultMap = $resultMap->resolveSubMap($registry,$row); + + if($resultObject===null) + $resultObject = $resultMap->createInstanceOfResult($registry); + + if(is_object($resultObject)) + { + if(strlen($resultMap->getGroupBy()) > 0) + return $this->addResultMapGroupBy($resultMap, $row, $parentGroup, $resultObject); + else + foreach($resultMap->getColumns() as $property) + $this->setObjectProperty($resultMap, $property, $row, $resultObject); + } + else + { + $resultObject = $this->fillDefaultResultMap($resultMap, $row, $resultObject); + } + return $resultObject; + } + + /** + * ResultMap with GroupBy property. Save object collection graph in a tree + * and collect the result later. + * @param TResultMap result mapping details. + * @param array a result set row retrieved from the database + * @param object the result object + * @return object result object. + */ + protected function addResultMapGroupBy($resultMap, $row, $parent, &$resultObject) + { + $group = $this->getResultMapGroupKey($resultMap, $row); + + if(empty($parent)) + { + $rootObject = array('object'=>$resultObject, 'property' => null); + $this->_groupBy->add(null, $group, $rootObject); + } + + foreach($resultMap->getColumns() as $property) + { + //set properties. + $this->setObjectProperty($resultMap, $property, $row, $resultObject); + $nested = $property->getResultMapping(); + + //nested property + if($this->getManager()->getResultMaps()->contains($nested)) + { + $nestedMap = $this->getManager()->getResultMap($nested); + $groupKey = $this->getResultMapGroupKey($nestedMap, $row); + + //add the node reference first + if(empty($parent)) + $this->_groupBy->add($group, $groupKey, ''); + + //get the nested result mapping value + $value = $this->fillResultMap($nested, $row, $groupKey); + + //add it to the object tree graph + $groupObject = array('object'=>$value, 'property' => $property->getProperty()); + if(empty($parent)) + $this->_groupBy->add($group, $groupKey, $groupObject); + else + $this->_groupBy->add($parent, $groupKey, $groupObject); + } + } + return $resultObject; + } + + /** + * Gets the result 'group by' groupping key for each row. + * @param TResultMap result mapping details. + * @param array a result set row retrieved from the database + * @return string groupping key. + */ + protected function getResultMapGroupKey($resultMap, $row) + { + $groupBy = $resultMap->getGroupBy(); + if(isset($row[$groupBy])) + return $resultMap->getID().$row[$groupBy]; + else + return $resultMap->getID().crc32(serialize($row)); + } + + /** + * Fill the result map using default settings. If $resultMap is null + * the result object returned will be guessed from $resultObject. + * @param TResultMap result mapping details. + * @param array a result set row retrieved from the database + * @param object the result object + * @return mixed the result object filled with data. + */ + protected function fillDefaultResultMap($resultMap, $row, $resultObject) + { + if($resultObject===null) + $resultObject=''; + + if($resultMap!==null) + $result = $this->fillArrayResultMap($resultMap, $row, $resultObject); + else + $result = $row; + + //if scalar result types + if(count($result) == 1 && ($type = gettype($resultObject))!= 'array') + return $this->getScalarResult($result, $type); + else + return $result; + } + + /** + * Retrieve the result map as an array. + * @param TResultMap result mapping details. + * @param array a result set row retrieved from the database + * @param object the result object + * @return array array list of result objects. + */ + protected function fillArrayResultMap($resultMap, $row, $resultObject) + { + $result = array(); + $registry=$this->getManager()->getTypeHandlers(); + foreach($resultMap->getColumns() as $column) + { + if(($column->getType()===null) + && ($resultObject!==null) && !is_object($resultObject)) + $column->setType(gettype($resultObject)); + $result[$column->getProperty()] = $column->getPropertyValue($registry,$row); + } + return $result; + } + + /** + * Converts the first array value to scalar value of given type. + * @param array list of results + * @param string scalar type. + * @return mixed scalar value. + */ + protected function getScalarResult($result, $type) + { + $scalar = array_shift($result); + settype($scalar, $type); + return $scalar; + } + + /** + * Set a property of the result object with appropriate value. + * @param TResultMap result mapping details. + * @param TResultProperty the result property to fill. + * @param array a result set row retrieved from the database + * @param object the result object + */ + protected function setObjectProperty($resultMap, $property, $row, &$resultObject) + { + $select = $property->getSelect(); + $key = $property->getProperty(); + $nested = $property->getNestedResultMap(); + $registry=$this->getManager()->getTypeHandlers(); + if($key === '') + { + $resultObject = $property->getPropertyValue($registry,$row); + } + else if(strlen($select) == 0 && ($nested===null)) + { + $value = $property->getPropertyValue($registry,$row); + + $this->_IsRowDataFound = $this->_IsRowDataFound || ($value != null); + if(is_array($resultObject) || is_object($resultObject)) + TPropertyAccess::set($resultObject, $key, $value); + else + $resultObject = $value; + } + else if($nested!==null) + { + if($property->instanceOfListType($resultObject) || $property->instanceOfArrayType($resultObject)) + { + if(strlen($resultMap->getGroupBy()) <= 0) + throw new TSqlMapExecutionException( + 'sqlmap_non_groupby_array_list_type', $resultMap->getID(), + get_class($resultObject), $key); + } + else + { + $obj = $nested->createInstanceOfResult($this->getManager()->getTypeHandlers()); + if($this->fillPropertyWithResultMap($nested, $row, $obj) == false) + $obj = null; + TPropertyAccess::set($resultObject, $key, $obj); + } + } + else //'select' ResultProperty + { + $this->enquequePostSelect($select, $resultMap, $property, $row, $resultObject); + } + } + + /** + * Add nested result property to post select queue. + * @param string post select statement ID + * @param TResultMap current result mapping details. + * @param TResultProperty current result property. + * @param array a result set row retrieved from the database + * @param object the result object + */ + protected function enquequePostSelect($select, $resultMap, $property, $row, $resultObject) + { + $statement = $this->getManager()->getMappedStatement($select); + $key = $this->getPostSelectKeys($resultMap, $property, $row); + $postSelect = new TPostSelectBinding; + $postSelect->setStatement($statement); + $postSelect->setResultObject($resultObject); + $postSelect->setResultProperty($property); + $postSelect->setKeys($key); + + if($property->instanceOfListType($resultObject)) + { + $values = null; + if($property->getLazyLoad()) + { + $values = TLazyLoadList::newInstance($statement, $key, + $resultObject, $property->getProperty()); + TPropertyAccess::set($resultObject, $property->getProperty(), $values); + } + else + $postSelect->setMethod(self::QUERY_FOR_LIST); + } + else if($property->instanceOfArrayType($resultObject)) + $postSelect->setMethod(self::QUERY_FOR_ARRAY); + else + $postSelect->setMethod(self::QUERY_FOR_OBJECT); + + if(!$property->getLazyLoad()) + $this->_selectQueue[] = $postSelect; + } + + /** + * Finds in the post select property the SQL statement primary selection keys. + * @param TResultMap result mapping details + * @param TResultProperty result property + * @param array current row data. + * @return array list of primary key values. + */ + protected function getPostSelectKeys($resultMap, $property,$row) + { + $value = $property->getColumn(); + if(is_int(strpos($value.',',0)) || is_int(strpos($value, '=',0))) + { + $keys = array(); + foreach(explode(',', $value) as $entry) + { + $pair =explode('=',$entry); + $keys[trim($pair[0])] = $row[trim($pair[1])]; + } + return $keys; + } + else + { + $registry=$this->getManager()->getTypeHandlers(); + return $property->getPropertyValue($registry,$row); + } + } + + /** + * Fills the property with result mapping results. + * @param TResultMap nested result mapping details. + * @param array a result set row retrieved from the database + * @param object the result object + * @return boolean true if the data was found, false otherwise. + */ + protected function fillPropertyWithResultMap($resultMap, $row, &$resultObject) + { + $dataFound = false; + foreach($resultMap->getColumns() as $property) + { + $this->_IsRowDataFound = false; + $this->setObjectProperty($resultMap, $property, $row, $resultObject); + $dataFound = $dataFound || $this->_IsRowDataFound; + } + $this->_IsRowDataFound = $dataFound; + return $dataFound; + } + + public function __wakeup() + { + parent::__wakeup(); + if (is_null($this->_selectQueue)) $this->_selectQueue = array(); + } + + public function __sleep() + { + $exprops = array(); $cn = __CLASS__; + if (!count($this->_selectQueue)) $exprops[] = "\0$cn\0_selectQueue"; + if (is_null($this->_groupBy)) $exprops[] = "\0$cn\0_groupBy"; + if (!$this->_IsRowDataFound) $exprops[] = "\0$cn\0_IsRowDataFound"; + return array_diff(parent::__sleep(),$exprops); + } +} + +/** + * TPostSelectBinding class. + * + * @author Wei Zhuo + * @version $Id$ + * @package System.Data.SqlMap.Statements + * @since 3.1 + */ +class TPostSelectBinding +{ + private $_statement=null; + private $_property=null; + private $_resultObject=null; + private $_keys=null; + private $_method=TMappedStatement::QUERY_FOR_LIST; + + public function getStatement(){ return $this->_statement; } + public function setStatement($value){ $this->_statement = $value; } + + public function getResultProperty(){ return $this->_property; } + public function setResultProperty($value){ $this->_property = $value; } + + public function getResultObject(){ return $this->_resultObject; } + public function setResultObject($value){ $this->_resultObject = $value; } + + public function getKeys(){ return $this->_keys; } + public function setKeys($value){ $this->_keys = $value; } + + public function getMethod(){ return $this->_method; } + public function setMethod($value){ $this->_method = $value; } +} + +/** + * TSQLMapObjectCollectionTree class. + * + * Maps object collection graphs as trees. Nodes in the collection can + * be {@link add} using parent relationships. The object collections can be + * build using the {@link collect} method. + * + * @author Wei Zhuo + * @version $Id$ + * @package System.Data.SqlMap.Statements + * @since 3.1 + */ +class TSqlMapObjectCollectionTree extends TComponent +{ + /** + * @var array object graph as tree + */ + private $_tree = array(); + /** + * @var array tree node values + */ + private $_entries = array(); + /** + * @var array resulting object collection + */ + private $_list = array(); + + /** + * @return boolean true if the graph is empty + */ + public function isEmpty() + { + return count($this->_entries) == 0; + } + + /** + * Add a new node to the object tree graph. + * @param string parent node id + * @param string new node id + * @param mixed node value + */ + public function add($parent, $node, $object='') + { + if(isset($this->_entries[$parent]) && ($this->_entries[$parent]!==null) + && isset($this->_entries[$node]) && ($this->_entries[$node]!==null)) + { + $this->_entries[$node] = $object; + return; + } + $this->_entries[$node] = $object; + if(empty($parent)) + { + if(isset($this->_entries[$node])) + return; + $this->_tree[$node] = array(); + } + $found = $this->addNode($this->_tree, $parent, $node); + if(!$found && !empty($parent)) + { + $this->_tree[$parent] = array(); + if(!isset($this->_entries[$parent]) || $object !== '') + $this->_entries[$parent] = $object; + $this->addNode($this->_tree, $parent, $node); + } + } + + /** + * Find the parent node and add the new node as its child. + * @param array list of nodes to check + * @param string parent node id + * @param string new node id + * @return boolean true if parent node is found. + */ + protected function addNode(&$childs, $parent, $node) + { + $found = false; + reset($childs); + for($i = 0, $k = count($childs); $i < $k; $i++) + { + $key = key($childs); + next($childs); + if($key == $parent) + { + $found = true; + $childs[$key][$node] = array(); + } + else + { + $found = $found || $this->addNode($childs[$key], $parent, $node); + } + } + return $found; + } + + /** + * @return array object collection + */ + public function collect() + { + while(count($this->_tree) > 0) + $this->collectChildren(null, $this->_tree); + return $this->getCollection(); + } + + /** + * @param array list of nodes to check + * @return boolean true if all nodes are leaf nodes, false otherwise + */ + protected function hasChildren(&$nodes) + { + $hasChildren = false; + foreach($nodes as $node) + if(count($node) != 0) + return true; + return $hasChildren; + } + + /** + * Visit all the child nodes and collect them by removing. + * @param string parent node id + * @param array list of child nodes. + */ + protected function collectChildren($parent, &$nodes) + { + $noChildren = !$this->hasChildren($nodes); + $childs = array(); + for(reset($nodes); $key = key($nodes);) + { + next($nodes); + if($noChildren) + { + $childs[] = $key; + unset($nodes[$key]); + } + else + $this->collectChildren($key, $nodes[$key]); + } + if(count($childs) > 0) + $this->onChildNodesVisited($parent, $childs); + } + + /** + * Set the object properties for all the child nodes visited. + * @param string parent node id + * @param array list of child nodes visited. + */ + protected function onChildNodesVisited($parent, $nodes) + { + if(empty($parent) || empty($this->_entries[$parent])) + return; + + $parentObject = $this->_entries[$parent]['object']; + $property = $this->_entries[$nodes[0]]['property']; + + $list = TPropertyAccess::get($parentObject, $property); + + foreach($nodes as $node) + { + if($list instanceof TList) + $parentObject->{$property}[] = $this->_entries[$node]['object']; + else if(is_array($list)) + $list[] = $this->_entries[$node]['object']; + else + throw new TSqlMapExecutionException( + 'sqlmap_property_must_be_list'); + } + + if(is_array($list)) + TPropertyAccess::set($parentObject, $property, $list); + + if($this->_entries[$parent]['property'] === null) + $this->_list[] = $parentObject; + } + + /** + * @return array object collection. + */ + protected function getCollection() + { + return $this->_list; + } + + public function __sleep() + { + $exprops = array(); $cn = __CLASS__; + if (!count($this->_tree)) $exprops[] = "\0$cn\0_tree"; + if (!count($this->_entries)) $exprops[] = "\0$cn\0_entries"; + if (!count($this->_list)) $exprops[] = "\0$cn\0_list"; + return array_diff(parent::__sleep(),$exprops); + } +} + +/** + * TResultSetListItemParameter class + * + * @author Wei Zhuo + * @version $Id$ + * @package System.Data.SqlMap.Statements + * @since 3.1 + */ +class TResultSetListItemParameter extends TComponent +{ + private $_resultObject; + private $_parameterObject; + private $_list; + + public function __construct($result, $parameter, &$list) + { + $this->_resultObject = $result; + $this->_parameterObject = $parameter; + $this->_list = &$list; + } + + public function getResult() + { + return $this->_resultObject; + } + + public function getParameter() + { + return $this->_parameterObject; + } + + public function &getList() + { + return $this->_list; + } +} + +/** + * TResultSetMapItemParameter class. + * + * @author Wei Zhuo + * @version $Id$ + * @package System.Data.SqlMap.Statements + * @since 3.1 + */ +class TResultSetMapItemParameter extends TComponent +{ + private $_key; + private $_value; + private $_parameterObject; + private $_map; + + public function __construct($key, $value, $parameter, &$map) + { + $this->_key = $key; + $this->_value = $value; + $this->_parameterObject = $parameter; + $this->_map = &$map; + } + + public function getKey() + { + return $this->_key; + } + + public function getValue() + { + return $this->_value; + } + + public function getParameter() + { + return $this->_parameterObject; + } + + public function &getMap() + { + return $this->_map; + } +} + -- cgit v1.2.3