Subversion-Projekte lars-tiefland.prado

Revision

Blame | Letzte Änderung | Log anzeigen | RSS feed

<?php
/**
 * TMappedStatement and related classes.
 *
 * @author Wei Zhuo <weizhuo[at]gmail[dot]com>
 * @link http://www.pradosoft.com/
 * @copyright Copyright &copy; 2005-2008 PradoSoft
 * @license http://www.pradosoft.com/license/
 * @version $Id: TMappedStatement.php 2582 2008-12-03 07:36:59Z christophe.boulain $
 * @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 <weizhuo[at]gmail[dot]com>
 * @version $Id: TMappedStatement.php 2582 2008-12-03 07:36:59Z christophe.boulain $
 * @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 $_selectQueque=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
         * <tt>executeQueryForList()</tt> 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(!is_null($delegate))
                {
                        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
         * <tt>executeQueryForMap()</tt> 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(!is_null($delegate))
                {
                        //while($row = $recordSet->fetchRow())
                        foreach($reader as $row)
                        {
                                $obj = $this->applyResultMap($row);
                                $key = TPropertyAccess::get($obj, $keyProperty);
                                $value = is_null($valueProperty) ? $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] = is_null($valueProperty) ? $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 <tt>$result</tt> 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 <tt>$result</tt> passed in as a parameter.
         *
         * This method should only be called by internal developers, consider using
         * <tt>executeQueryForObject()</tt> 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(is_null($generatedKey))
                        $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(!is_null($selectKey) && !$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(!is_null($selectKey) && $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->_selectQueque))
                {
                        $postSelect = array_shift($this->_selectQueque);
                        $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(is_null($resultObject))
                {
                        $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(is_null($resultObject))
                        $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 <tt>$resultMap</tt> is null
         * the result object returned will be guessed from <tt>$resultObject</tt>.
         * @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(is_null($resultObject))
                        $resultObject='';

                if(!is_null($resultMap))
                        $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(is_null($column->getType())
                                && !is_null($resultObject) && !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 && is_null($nested))
                {
                        $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(!is_null($nested))
                {
                        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())
                        array_push($this->_selectQueque, $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;
        }
}

/**
 * TPostSelectBinding class.
 *
 * @author Wei Zhuo <weizho[at]gmail[dot]com>
 * @version $Id: TMappedStatement.php 2582 2008-12-03 07:36:59Z christophe.boulain $
 * @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 <weizhuo[at]gmail[dot]com>
 * @version $Id: TMappedStatement.php 2582 2008-12-03 07:36:59Z christophe.boulain $
 * @package System.Data.SqlMap.Statements
 * @since 3.1
 */
class TSqlMapObjectCollectionTree
{
        /**
         * @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]) && !is_null($this->_entries[$parent])
                        && isset($this->_entries[$node]) && !is_null($this->_entries[$node]))
                {
                        $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;
        }
}

/**
 * TResultSetListItemParameter class
 *
 * @author Wei Zhuo <weizho[at]gmail[dot]com>
 * @version $Id: TMappedStatement.php 2582 2008-12-03 07:36:59Z christophe.boulain $
 * @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 <weizho[at]gmail[dot]com>
 * @version $Id: TMappedStatement.php 2582 2008-12-03 07:36:59Z christophe.boulain $
 * @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;
        }
}