Coverage for Doctrine_Hydrate

Back to coverage report

1 <?php
2 /*
3  *  $Id: Hydrate.php 2832 2007-10-13 19:30:45Z zYne $
4  *
5  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
6  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
7  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
8  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
9  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
10  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
11  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
12  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
13  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
14  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
15  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
16  *
17  * This software consists of voluntary contributions made by many individuals
18  * and is licensed under the LGPL. For more information, see
19  * <http://www.phpdoctrine.com>.
20  */
21
22 /**
23  * Doctrine_Hydrate is a base class for Doctrine_RawSql and Doctrine_Query.
24  * Its purpose is to populate object graphs.
25  *
26  *
27  * @package     Doctrine
28  * @subpackage  Hydrate
29  * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
30  * @link        www.phpdoctrine.com
31  * @since       1.0
32  * @version     $Revision: 2832 $
33  * @author      Konsta Vesterinen <kvesteri@cc.hut.fi>
34  */
35 class Doctrine_Hydrate extends Doctrine_Locator_Injectable implements Serializable
36 {
37     /**
38      * QUERY TYPE CONSTANTS
39      */
40
41     /**
42      * constant for SELECT queries
43      */
44     const SELECT = 0;
45     /**
46      * constant for DELETE queries
47      */
48     const DELETE = 1;
49     /**
50      * constant for UPDATE queries
51      */
52     const UPDATE = 2;
53     /**
54      * constant for INSERT queries
55      */
56     const INSERT = 3;
57     /**
58      * constant for CREATE queries
59      */
60     const CREATE = 4;
61
62     /**
63      * @var array $params                       query input parameters
64      */
65     protected $_params      = array('where' => array(),
66                                     'set' => array(),
67                                     'having' => array());
68     /**
69      * @var Doctrine_Connection $conn           Doctrine_Connection object
70      */
71     protected $_conn;
72     /**
73      * @var Doctrine_View $_view                Doctrine_View object, when set this object will use the
74      *                                          the query given by the view object for object population
75      */
76     protected $_view;
77     /**
78      * @var array $_aliasMap                    two dimensional array containing the map for query aliases
79      *      Main keys are component aliases
80      *
81      *          table               table object associated with given alias
82      *
83      *          relation            the relation object owned by the parent
84      *
85      *          parent              the alias of the parent
86      *
87      *          agg                 the aggregates of this component
88      *
89      *          map                 the name of the column / aggregate value this
90      *                              component is mapped to a collection
91      */
92     protected $_aliasMap         = array();
93     /**
94      *
95      */
96     protected $pendingAggregates = array();
97     /**
98      * @var array $aggregateMap             an array containing all aggregate aliases, keys as dql aliases
99      *                                      and values as sql aliases
100      */
101     protected $aggregateMap      = array();
102     /**
103      * @var array $_options                 an array of options
104      */
105     protected $_options    = array(
106                             'fetchMode'      => Doctrine::FETCH_RECORD,
107                             'parserCache'    => false,
108                             'resultSetCache' => false,
109                             );
110     /**
111      * @var string $_sql            cached SQL query
112      */
113     protected $_sql;
114     /**
115      * @var array $parts            SQL query string parts
116      */
117     protected $parts = array(
118         'select'    => array(),
119         'distinct'  => false,
120         'forUpdate' => false,
121         'from'      => array(),
122         'set'       => array(),
123         'join'      => array(),
124         'where'     => array(),
125         'groupby'   => array(),
126         'having'    => array(),
127         'orderby'   => array(),
128         'limit'     => false,
129         'offset'    => false,
130         );
131     /**
132      * @var integer $type                   the query type
133      *
134      * @see Doctrine_Query::* constants
135      */
136     protected $type            = self::SELECT;
137     /**
138      * @var array
139      */
140     protected $_cache;
141     /**
142      * The current hydration mode.
143      */
144     protected $_hydrationMode = Doctrine::HYDRATE_RECORD;
145     /**
146      * @var boolean $_expireCache           a boolean value that indicates whether or not to force cache expiration
147      */
148     protected $_expireCache     = false;
149
150     protected $_timeToLive;
151
152     protected $_tableAliases    = array();
153     /**
154      * @var array $_tableAliasSeeds         A simple array keys representing table aliases and values
155      *                                      as table alias seeds. The seeds are used for generating short table
156      *                                      aliases.
157      */
158     protected $_tableAliasSeeds = array();
159     /**
160      * constructor
161      *
162      * @param Doctrine_Connection|null $connection
163      */
164     public function __construct($connection = null)
165     {
166         if ( ! ($connection instanceof Doctrine_Connection)) {
167             $connection = Doctrine_Manager::getInstance()->getCurrentConnection();
168         }
169         $this->_conn = $connection;
170     }
171     /**
172      * getRootAlias
173      * returns the alias of the the root component
174      *
175      * @return array
176      */
177     public function getRootAlias()
178     {
179         if ( ! $this->_aliasMap) {
180           $this->getSql();
181         }
182         
183         reset($this->_aliasMap);
184
185         return key($this->_aliasMap);
186     }
187     /**
188      * getRootDeclaration
189      * returns the root declaration
190      *
191      * @return array
192      */
193     public function getRootDeclaration()
194     {
195         $map = reset($this->_aliasMap);
196
197         return $map;
198     }
199     /**
200      * getRoot
201      * returns the root component for this object
202      *
203      * @return Doctrine_Table       root components table
204      */
205     public function getRoot()
206     {
207         $map = reset($this->_aliasMap);
208
209         if ( ! isset($map['table'])) {
210             throw new Doctrine_Hydrate_Exception('Root component not initialized.');
211         }
212
213         return $map['table'];
214     }
215     /**
216      * getSql
217      * return the sql associated with this object
218      *
219      * @return string   sql query string
220      */
221     public function getSql()
222     {
223         return $this->getQuery();
224     }
225     /**
226      * useCache
227      *
228      * @param Doctrine_Cache_Interface|bool $driver      cache driver
229      * @param integer $timeToLive                        how long the cache entry is valid
230      * @return Doctrine_Hydrate         this object
231      */
232     public function useCache($driver = true, $timeToLive = null)
233     {
234         if ($driver !== null) {
235             if ($driver !== true) {
236                 if ( ! ($driver instanceof Doctrine_Cache_Interface)) {
237                     $msg = 'First argument should be instance of Doctrine_Cache_Interface or null.';
238
239                     throw new Doctrine_Hydrate_Exception($msg);
240                 }
241             }
242         }
243         $this->_cache = $driver;
244
245         return $this->setCacheLifeSpan($timeToLive);
246     }
247     /**
248      * expireCache
249      *
250      * @param boolean $expire       whether or not to force cache expiration
251      * @return Doctrine_Hydrate     this object
252      */
253     public function expireCache($expire = true)
254     {
255         $this->_expireCache = true;
256
257         return $this;
258     }
259     /**
260      * setCacheLifeSpan
261      *
262      * @param integer $timeToLive   how long the cache entry is valid
263      * @return Doctrine_Hydrate     this object
264      */
265     public function setCacheLifeSpan($timeToLive)
266     {
267         if ($timeToLive !== null) {
268             $timeToLive = (int) $timeToLive;
269         }
270         $this->_timeToLive = $timeToLive;
271
272         return $this;
273     }
274     /**
275      * getCacheDriver
276      * returns the cache driver associated with this object
277      *
278      * @return Doctrine_Cache_Interface|boolean|null    cache driver
279      */
280     public function getCacheDriver()
281     {
282         if ($this->_cache instanceof Doctrine_Cache_Interface) {
283             return $this->_cache;
284         } else {
285             return $this->_conn->getCacheDriver();
286         }
287     }
288     /**
289      * Sets the fetchmode.
290      *
291      * @param integer $fetchmode  One of the Doctrine::HYDRATE_* constants.
292      */
293     public function setHydrationMode($hydrationMode)
294     {
295         $this->_hydrationMode = $hydrationMode;
296         return $this;
297     }
298     /**
299      * serialize
300      * this method is automatically called when this Doctrine_Hydrate is serialized
301      *
302      * @return array    an array of serialized properties
303      */
304     public function serialize()
305     {
306         $vars = get_object_vars($this);
307
308     }
309     /**
310      * unseralize
311      * this method is automatically called everytime a Doctrine_Hydrate object is unserialized
312      *
313      * @param string $serialized                Doctrine_Record as serialized string
314      * @return void
315      */
316     public function unserialize($serialized)
317     {
318
319     }
320     /**
321      * generateNewTableAlias
322      * generates a new alias from given table alias
323      *
324      * @param string $tableAlias    table alias from which to generate the new alias from
325      * @return string               the created table alias
326      */
327     public function generateNewTableAlias($tableAlias)
328     {
329         if (isset($this->_tableAliases[$tableAlias])) {
330             // generate a new alias
331             $name = substr($tableAlias, 0, 1);
332             $i    = ((int) substr($tableAlias, 1));
333
334             if ($i == 0) {
335                 $i = 1;
336             }
337
338             $newIndex  = ($this->_tableAliasSeeds[$name] + $i);
339
340             return $name . $newIndex;
341         }
342
343         return $tableAlias;
344     }
345     /**
346      * hasTableAlias
347      * whether or not this object has given tableAlias
348      *
349      * @param string $tableAlias    the table alias to be checked
350      * @return boolean              true if this object has given alias, otherwise false
351      */
352     public function hasTableAlias($tableAlias)
353     {
354         return (isset($this->_tableAliases[$tableAlias]));
355     }
356     /**
357      * getComponentAlias
358      * get component alias associated with given table alias
359      *
360      * @param string $tableAlias    the table alias that identifies the component alias
361      * @return string               component alias
362      */
363     public function getComponentAlias($tableAlias)
364     {
365         if ( ! isset($this->_tableAliases[$tableAlias])) {
366             throw new Doctrine_Hydrate_Exception('Unknown table alias ' . $tableAlias);
367         }
368         return $this->_tableAliases[$tableAlias];
369     }
370     /**
371      * getTableAliasSeed
372      * returns the alias seed for given table alias
373      *
374      * @param string $tableAlias    table alias that identifies the alias seed
375      * @return integer              table alias seed
376      */
377     public function getTableAliasSeed($tableAlias)
378     {
379         if ( ! isset($this->_tableAliasSeeds[$tableAlias])) {
380             return 0;
381         }
382         return $this->_tableAliasSeeds[$tableAlias];
383     }
384     /**
385      * generateTableAlias
386      * generates a table alias from given table name and associates 
387      * it with given component alias
388      *
389      * @param string $componentAlias    the component alias to be associated with generated table alias
390      * @param string $tableName         the table name from which to generate the table alias
391      * @return string                   the generated table alias
392      */
393     public function generateTableAlias($componentAlias, $tableName)
394     {
395         $char   = strtolower(substr($tableName, 0, 1));
396
397         $alias  = $char;
398
399         if ( ! isset($this->_tableAliasSeeds[$alias])) {
400             $this->_tableAliasSeeds[$alias] = 1;
401         }
402
403         while (isset($this->_tableAliases[$alias])) {
404             if ( ! isset($this->_tableAliasSeeds[$alias])) {
405                 $this->_tableAliasSeeds[$alias] = 1;
406             }
407             $alias = $char . ++$this->_tableAliasSeeds[$alias];
408         }
409
410         $this->_tableAliases[$alias] = $componentAlias;
411
412         return $alias;
413     }
414     /**
415      * getTableAliases
416      * returns all table aliases
417      *
418      * @return array        table aliases as an array
419      */
420     public function getTableAliases()
421     {
422         return $this->_tableAliases;
423     }
424     /** 
425      * addTableAlias
426      * adds an alias for table and associates it with given component alias
427      *
428      * @param string $componentAlias    the alias for the query component associated with given tableAlias
429      * @param string $tableAlias        the table alias to be added
430      * @return Doctrine_Hydrate
431      */
432     public function addTableAlias($tableAlias, $componentAlias)
433     {
434         $this->_tableAliases[$tableAlias] = $componentAlias;
435
436         return $this;
437     }
438     /**
439      * getTableAlias
440      * some database such as Oracle need the identifier lengths to be < ~30 chars
441      * hence Doctrine creates as short identifier aliases as possible
442      *
443      * this method is used for the creation of short table aliases, its also
444      * smart enough to check if an alias already exists for given component (componentAlias)
445      *
446      * @param string $componentAlias    the alias for the query component to search table alias for
447      * @param string $tableName         the table name from which the table alias is being created
448      * @return string                   the generated / fetched short alias
449      */
450     public function getTableAlias($componentAlias, $tableName = null)
451     {
452         $alias = array_search($componentAlias, $this->_tableAliases);
453
454         if ($alias !== false) {
455             return $alias;
456         }
457
458         if ($tableName === null) {
459             throw new Doctrine_Hydrate_Exception("Couldn't get short alias for " . $componentAlias);
460         }
461
462         return $this->generateTableAlias($componentAlias, $tableName);
463     }
464     /**
465      * addQueryPart
466      * adds a query part in the query part array
467      *
468      * @param string $name          the name of the query part to be added
469      * @param string $part          query part string
470      * @throws Doctrine_Hydrate_Exception   if trying to add unknown query part
471      * @return Doctrine_Hydrate     this object
472      */
473     public function addQueryPart($name, $part)
474     {
475         if ( ! isset($this->parts[$name])) {
476             throw new Doctrine_Hydrate_Exception('Unknown query part ' . $name);
477         }
478         if (is_array($part)) {
479             $this->parts[$name] = array_merge($this->parts[$name], $part);
480         } else {
481             $this->parts[$name][] = $part;
482         }
483         return $this;
484     }
485     /**
486      * setQueryPart
487      * sets a query part in the query part array
488      *
489      * @param string $name          the name of the query part to be set
490      * @param string $part          query part string
491      * @throws Doctrine_Hydrate_Exception   if trying to set unknown query part
492      * @return Doctrine_Hydrate     this object
493      */
494     public function getQueryPart($part)
495     {
496         if ( ! isset($this->parts[$part])) {
497             throw new Doctrine_Hydrate_Exception('Unknown query part ' . $part);
498         }
499
500         return $this->parts[$part];
501     }
502     /**
503      * removeQueryPart
504      * removes a query part from the query part array
505      *
506      * @param string $name          the name of the query part to be removed
507      * @throws Doctrine_Hydrate_Exception   if trying to remove unknown query part
508      * @return Doctrine_Hydrate     this object
509      */
510     public function removeQueryPart($name)
511     {
512         if (isset($this->parts[$name])) {
513             if ($name == 'limit' || $name == 'offset') {
514                 $this->parts[$name] = false;
515             } else {
516                 $this->parts[$name] = array();
517             }
518         } else {
519             throw new Doctrine_Hydrate_Exception('Unknown query part ' . $name);
520         }
521         return $this;
522     }
523     /**
524      * setQueryPart
525      * sets a query part in the query part array
526      *
527      * @param string $name          the name of the query part to be set
528      * @param string $part          query part string
529      * @throws Doctrine_Hydrate_Exception   if trying to set unknown query part
530      * @return Doctrine_Hydrate     this object
531      */
532     public function setQueryPart($name, $part)
533     {
534         if ( ! isset($this->parts[$name])) {
535             throw new Doctrine_Hydrate_Exception('Unknown query part ' . $name);
536         }
537
538         if ($name !== 'limit' && $name !== 'offset') {
539             if (is_array($part)) {
540                 $this->parts[$name] = $part;
541             } else {
542                 $this->parts[$name] = array($part);
543             }
544         } else {
545             $this->parts[$name] = $part;
546         }
547
548         return $this;
549     }
550     /**
551      * hasAliasDeclaration
552      * whether or not this object has a declaration for given component alias
553      *
554      * @param string $componentAlias    the component alias the retrieve the declaration from
555      * @return boolean
556      */
557     public function hasAliasDeclaration($componentAlias)
558     {
559         return isset($this->_aliasMap[$componentAlias]);
560     }
561     /**
562      * getAliasDeclaration
563      * get the declaration for given component alias
564      *
565      * @param string $componentAlias    the component alias the retrieve the declaration from
566      * @return array                    the alias declaration
567      */
568     public function getAliasDeclaration($componentAlias)
569     {
570         if ( ! isset($this->_aliasMap[$componentAlias])) {
571             throw new Doctrine_Hydrate_Exception('Unknown component alias ' . $componentAlias);
572         }
573
574         return $this->_aliasMap[$componentAlias];
575     }
576     /**
577      * copyAliases
578      * copy aliases from another Hydrate object
579      *
580      * this method is needed by DQL subqueries which need the aliases
581      * of the parent query
582      *
583      * @param Doctrine_Hydrate $query   the query object from which the
584      *                                  aliases are copied from
585      * @return Doctrine_Hydrate         this object
586      */
587     public function copyAliases(Doctrine_Hydrate $query)
588     {
589         $this->_tableAliases = $query->_tableAliases;
590         $this->_aliasMap     = $query->_aliasMap;
591         $this->_tableAliasSeeds = $query->_tableAliasSeeds;
592         return $this;
593     }
594     /**
595      * createSubquery
596      * creates a subquery
597      *
598      * @return Doctrine_Hydrate
599      */
600     public function createSubquery()
601     {
602         $class = get_class($this);
603         $obj   = new $class();
604
605         // copy the aliases to the subquery
606         $obj->copyAliases($this);
607
608         // this prevents the 'id' being selected, re ticket #307
609         $obj->isSubquery(true);
610
611         return $obj;
612     }
613     /**
614      * limitSubqueryUsed
615      * whether or not limit subquery was used
616      *
617      * @return boolean
618      */
619     public function isLimitSubqueryUsed()
620     {
621         return false;
622     }
623     /**
624      * clear
625      * resets all the variables
626      *
627      * @return void
628      */
629     protected function clear()
630     {
631         $this->parts = array(
632                     'select'    => array(),
633                     'distinct'  => false,
634                     'forUpdate' => false,
635                     'from'      => array(),
636                     'set'       => array(),
637                     'join'      => array(),
638                     'where'     => array(),
639                     'groupby'   => array(),
640                     'having'    => array(),
641                     'orderby'   => array(),
642                     'limit'     => false,
643                     'offset'    => false,
644                     );
645         $this->inheritanceApplied = false;
646     }
647     /**
648      * getConnection
649      *
650      * @return Doctrine_Connection
651      */
652     public function getConnection()
653     {
654         return $this->_conn;
655     }
656     /**
657      * setView
658      * sets a database view this query object uses
659      * this method should only be called internally by doctrine
660      *
661      * @param Doctrine_View $view       database view
662      * @return void
663      */
664     public function setView(Doctrine_View $view)
665     {
666         $this->_view = $view;
667     }
668     /**
669      * getView
670      * returns the view associated with this query object (if any)
671      *
672      * @return Doctrine_View        the view associated with this query object
673      */
674     public function getView()
675     {
676         return $this->_view;
677     }
678     /**
679      * getParams
680      *
681      * @return array
682      */
683     public function getParams()
684     {
685         return array_merge($this->_params['set'], $this->_params['where'], $this->_params['having']);
686     }
687     /**
688      * setParams
689      *
690      * @param array $params
691      */
692     public function setParams(array $params = array()) {
693         $this->_params = $params;
694     }
695     public function convertEnums($params)
696     {
697         return $params;
698     }
699     /**
700      * setAliasMap
701      * sets the whole component alias map
702      *
703      * @param array $map            alias map
704      * @return Doctrine_Hydrate     this object
705      */
706     public function setAliasMap(array $map)
707     {
708         $this->_aliasMap = $map;
709
710         return $this;
711     }
712     /**
713      * getAliasMap
714      * returns the component alias map
715      *
716      * @return array    component alias map
717      */
718     public function getAliasMap()
719     {
720         return $this->_aliasMap;
721     }
722     /**
723      * getCachedForm
724      * returns the cached form of this query for given resultSet
725      *
726      * @param array $resultSet
727      * @return string           serialized string representation of this query
728      */
729     public function getCachedForm(array $resultSet)
730     {
731         $map = '';
732
733         foreach ($this->getAliasMap() as $k => $v) {
734             if ( ! isset($v['parent'])) {
735                 $map[$k][] = $v['table']->getComponentName();
736             } else {
737                 $map[$k][] = $v['parent'] . '.' . $v['relation']->getAlias();
738             }
739             if (isset($v['agg'])) {
740                 $map[$k][] = $v['agg'];
741             }
742         }
743
744         return serialize(array($resultSet, $map, $this->getTableAliases()));
745     }
746     public function _execute($params)
747     {
748         $params = $this->_conn->convertBooleans($params);
749
750         if ( ! $this->_view) {
751             $query = $this->getQuery($params);
752         } else {
753             $query = $this->_view->getSelectSql();
754         }
755
756         $params = $this->convertEnums($params);
757
758         if ($this->isLimitSubqueryUsed() &&
759             $this->_conn->getAttribute(Doctrine::ATTR_DRIVER_NAME) !== 'mysql') {
760
761             $params = array_merge($params, $params);
762         }
763
764         if ($this->type !== self::SELECT) {
765             return $this->_conn->exec($query, $params);
766         }
767
768         $stmt = $this->_conn->execute($query, $params);
769         return $stmt;
770     }
771     /**
772      * execute
773      * executes the query and populates the data set
774      *
775      * @param string $params
776      * @return Doctrine_Collection            the root collection
777      */
778     public function execute($params = array(), $hydrationMode = null)
779     {
780         $params = array_merge($this->_params['set'], 
781                               $this->_params['where'],
782                               $this->_params['having'], 
783                               $params);
784         if ($this->_cache) {
785             $cacheDriver = $this->getCacheDriver();
786
787             $dql  = $this->getDql();
788             // calculate hash for dql query
789             $hash = md5($dql . var_export($params, true));
790
791             $cached = ($this->_expireCache) ? false : $cacheDriver->fetch($hash);
792
793
794             if ($cached === false) {
795                 // cache miss
796                 $stmt = $this->_execute($params);
797                 $array = $this->parseData2($stmt, Doctrine::HYDRATE_ARRAY);
798
799                 $cached = $this->getCachedForm($array);
800
801                 $cacheDriver->save($hash, $cached, $this->_timeToLive);
802             } else {
803                 $cached = unserialize($cached);
804                 $this->_tableAliases = $cached[2];
805                 $array = $cached[0];
806
807                 $map   = array();
808                 foreach ($cached[1] as $k => $v) {
809                     $e = explode('.', $v[0]);
810                     if (count($e) === 1) {
811                         $map[$k]['table'] = $this->_conn->getTable($e[0]);
812                     } else {
813                         $map[$k]['parent']   = $e[0];
814                         $map[$k]['relation'] = $map[$e[0]]['table']->getRelation($e[1]);
815                         $map[$k]['table']    = $map[$k]['relation']->getTable();
816                     }
817                     if (isset($v[1])) {
818                         $map[$k]['agg'] = $v[1];
819                     }
820                 }
821                 $this->_aliasMap = $map;
822             }
823         } else {
824             $stmt = $this->_execute($params);
825
826             if (is_integer($stmt)) {
827                 return $stmt;
828             }
829
830             $array = $this->parseData2($stmt, $hydrationMode);
831         }
832         return $array;
833     }
834
835     /**
836      * getType
837      *
838      * returns the type of this query object
839      * by default the type is Doctrine_Hydrate::SELECT but if update() or delete()
840      * are being called the type is Doctrine_Hydrate::UPDATE and Doctrine_Hydrate::DELETE,
841      * respectively
842      *
843      * @see Doctrine_Hydrate::SELECT
844      * @see Doctrine_Hydrate::UPDATE
845      * @see Doctrine_Hydrate::DELETE
846      *
847      * @return integer      return the query type
848      */
849     public function getType()
850     {
851         return $this->type;
852     }
853     /**
854      * applyInheritance
855      * applies column aggregation inheritance to DQL / SQL query
856      *
857      * @return string
858      */
859     public function applyInheritance()
860     {
861         // get the inheritance maps
862         $array = array();
863
864         foreach ($this->_aliasMap as $componentAlias => $data) {
865             $tableAlias = $this->getTableAlias($componentAlias);
866             $array[$tableAlias][] = $data['table']->inheritanceMap;
867         }
868
869         // apply inheritance maps
870         $str = '';
871         $c = array();
872
873         $index = 0;
874         foreach ($array as $tableAlias => $maps) {
875             $a = array();
876
877             // don't use table aliases if the query isn't a select query
878             if ($this->type !== Doctrine_Query::SELECT) {
879                 $tableAlias = '';
880             } else {
881                 $tableAlias .= '.';
882             }
883
884             foreach ($maps as $map) {
885                 $b = array();
886                 foreach ($map as $field => $value) {
887                     $identifier = $this->_conn->quoteIdentifier($tableAlias . $field);
888
889                     if ($index > 0) {
890                         $b[] = '(' . $identifier . ' = ' . $this->_conn->quote($value)
891                              . ' OR ' . $identifier . ' IS NULL)';
892                     } else {
893                         $b[] = $identifier . ' = ' . $this->_conn->quote($value);
894                     }
895                 }
896
897                 if ( ! empty($b)) {
898                     $a[] = implode(' AND ', $b);
899                 }
900             }
901
902             if ( ! empty($a)) {
903                 $c[] = implode(' AND ', $a);
904             }
905             $index++;
906         }
907
908         $str .= implode(' AND ', $c);
909
910         return $str;
911     }
912     /**
913      * fetchArray
914      * Convenience method to execute using array fetching as hydration mode.
915      *
916      * @param string $params
917      * @return array
918      */
919     public function fetchArray($params = array()) {
920         return $this->execute($params, Doctrine::HYDRATE_ARRAY);
921     }
922     /**
923      * fetchOne
924      * Convenience method to execute the query and return the first item
925      * of the collection.
926      *
927      * @param string $params Parameters
928      * @param int $hydrationMode Hydration mode
929      * @return mixed Array or Doctrine_Collection or false if no result.
930      */
931     public function fetchOne($params = array(), $hydrationMode = null)
932     {
933         if (is_null($hydrationMode)) {
934             $hydrationMode = $this->_hydrationMode;
935         }
936
937         $collection = $this->execute($params, $hydrationMode);
938
939         if (count($collection) === 0) {
940             return false;
941         }
942
943         switch ($hydrationMode) {
944             case Doctrine::HYDRATE_RECORD:
945                 return $collection->getFirst();
946             case Doctrine::HYDRATE_ARRAY:
947                 return array_shift($collection);
948         }
949
950         return false;
951     }
952     /**
953      * parseData
954      * parses the data returned by statement object
955      *
956      * This is method defines the core of Doctrine object population algorithm
957      * hence this method strives to be as fast as possible
958      *
959      * The key idea is the loop over the rowset only once doing all the needed operations
960      * within this massive loop.
961      *
962      * @param mixed $stmt
963      * @return array
964      */
965     public function parseData2($stmt, $hydrationMode)
966     {
967
968         $cache = array();
969         $rootMap   = reset($this->_aliasMap);
970         $rootAlias = key($this->_aliasMap);
971         $componentName = $rootMap['table']->getComponentName();
972
973         if ($hydrationMode === null) {
974             $hydrationMode = $this->_hydrationMode;
975         }
976
977         if ($hydrationMode === Doctrine::HYDRATE_ARRAY) {
978             $driver = new Doctrine_Hydrate_Array();
979         } else {
980             $driver = new Doctrine_Hydrate_Record();
981         }
982
983         $array = $driver->getElementCollection($componentName);
984
985         if ($stmt === false || $stmt === 0) {
986             return $array;
987         }
988
989         $event = new Doctrine_Event(Doctrine_Event::HYDRATE, null);
990
991         // for every getRecordListener() there is a little bit 
992         // logic behind it, hence calling it multiple times on
993         // large result sets can be quite expensive.
994         // So for efficiency we use little listener caching here
995         foreach ($this->_aliasMap as $alias => $data) {
996             $componentName = $data['table']->getComponentName();
997             $listeners[$componentName] = $data['table']->getRecordListener();
998         }
999
1000         while ($data = $stmt->fetch(Doctrine::FETCH_ASSOC)) {
1001             $currData  = array();
1002             $identifiable = array();
1003
1004             foreach ($data as $key => $value) {
1005
1006                 // The following little cache solution ensures that field aliases are
1007                 // parsed only once. This increases speed on large result sets by an order
1008                 // of magnitude.
1009                 if ( ! isset($cache[$key])) {
1010                     $e = explode('__', $key);
1011                     $cache[$key]['field'] = $field = strtolower(array_pop($e));
1012                     $cache[$key]['alias'] = $this->_tableAliases[strtolower(implode('__', $e))];
1013                 }
1014
1015
1016                 $map   = $this->_aliasMap[$cache[$key]['alias']];
1017                 $table = $map['table'];
1018                 $alias = $cache[$key]['alias'];
1019                 $field = $cache[$key]['field'];
1020
1021                 if (isset($this->_aliasMap[$alias]['agg'][$field])) {
1022                     $field = $this->_aliasMap[$alias]['agg'][$field];
1023                 }
1024
1025
1026                 if ( ! isset($currData[$alias])) {
1027                     $currData[$alias] = array();
1028                 }
1029
1030                 if ( ! isset($prev[$alias])) {
1031                     $prev[$alias] = array();
1032                 }
1033
1034                 $currData[$alias][$field] = $table->prepareValue($field, $value);
1035                 if ($value !== null) {
1036                     $identifiable[$alias] = true;
1037                 }
1038             }
1039
1040
1041             // dealing with root component
1042             $table = $this->_aliasMap[$rootAlias]['table'];
1043             $componentName = $table->getComponentName();
1044             $event->set('data', $currData[$rootAlias]);
1045             $listeners[$componentName]->preHydrate($event);
1046             $element = $driver->getElement($currData[$rootAlias], $componentName);
1047
1048             $oneToOne = false;
1049
1050             $index = $driver->search($element, $array);
1051             if ($index === false) {
1052                 $event->set('data', $element);
1053                 $listeners[$componentName]->postHydrate($event);
1054
1055                 if (isset($this->_aliasMap[$rootAlias]['map'])) {
1056                     $key = $this->_aliasMap[$rootAlias]['map'];
1057
1058                     if (isset($array[$key])) {
1059                         throw new Doctrine_Hydrate_Exception("Couldn't hydrate. Found non-unique key mapping.");
1060                     }
1061
1062                     if ( ! isset($element[$key])) {
1063                         throw new Doctrine_Hydrate_Exception("Couldn't hydrate. Found a non-existent key.");
1064                     }
1065
1066                     $array[$element[$key]] = $element;
1067                 } else {
1068                     $array[] = $element;
1069                 }
1070             }
1071             $this->_setLastElement($prev, $array, $index, $rootAlias, $oneToOne);
1072             unset($currData[$rootAlias]);
1073
1074             foreach ($currData as $alias => $data) {
1075                 $index = false;
1076                 $map   = $this->_aliasMap[$alias];
1077                 $table = $this->_aliasMap[$alias]['table'];
1078                 $componentName = $table->getComponentName();
1079                 $event->set('data', $data);
1080                 $listeners[$componentName]->preHydrate($event);
1081
1082                 $element = $driver->getElement($data, $componentName);
1083
1084                 $parent   = $map['parent'];
1085                 $relation = $map['relation'];
1086                 $componentAlias = $map['relation']->getAlias();
1087
1088                 if ( ! isset($prev[$parent])) {
1089                     break;
1090                 }
1091
1092                 // check the type of the relation
1093                 if ( ! $relation->isOneToOne()) {
1094                     // initialize the collection
1095
1096                     if ($driver->initRelated($prev[$parent], $componentAlias)) {
1097
1098                         // append element
1099                         if (isset($identifiable[$alias])) {
1100                             $index = $driver->search($element, $prev[$parent][$componentAlias]);
1101
1102                             if ($index === false) {
1103                                 $event->set('data', $element);
1104                                 $listeners[$componentName]->postHydrate($event);
1105
1106                                 if (isset($map['map'])) {
1107                                     $key = $map['map'];
1108                                     if (isset($prev[$parent][$componentAlias][$key])) {
1109                                         throw new Doctrine_Hydrate_Exception("Couldn't hydrate. Found non-unique key mapping.");
1110                                     }
1111                                     if ( ! isset($element[$key])) {
1112                                         throw new Doctrine_Hydrate_Exception("Couldn't hydrate. Found a non-existent key.");
1113                                     }
1114                                     $prev[$parent][$componentAlias][$element[$key]] = $element;
1115                                 } else {
1116                                     $prev[$parent][$componentAlias][] = $element;
1117                                 }
1118                             }
1119                         }
1120                         // register collection for later snapshots
1121                         $driver->registerCollection($prev[$parent][$componentAlias]);
1122                     }
1123                 } else {
1124                     if ( ! isset($identifiable[$alias])) {
1125                         $prev[$parent][$componentAlias] = $driver->getNullPointer();
1126                     } else {
1127                         $prev[$parent][$componentAlias] = $element;
1128                     }
1129                     $oneToOne = true;
1130                 }
1131                 $coll =& $prev[$parent][$componentAlias];
1132                 $this->_setLastElement($prev, $coll, $index, $alias, $oneToOne);
1133             }
1134         }
1135
1136         $driver->flush();
1137
1138         $stmt->closeCursor();
1139         return $array;
1140     }
1141     /**
1142      * _setLastElement
1143      *
1144      * sets the last element of given data array / collection
1145      * as previous element
1146      *
1147      * @param boolean|integer $index
1148      * @return void
1149      */
1150     public function _setLastElement(&$prev, &$coll, $index, $alias, $oneToOne)
1151     {
1152         if ($coll === self::$_null) {
1153             return false;
1154         }
1155         if ($index !== false) {
1156             $prev[$alias] =& $coll[$index];
1157         } else {
1158             // first check the count (we do not want to get the last element
1159             // of an empty collection/array)
1160             if (count($coll) > 0) {
1161                 if (is_array($coll)) {
1162                     if ($oneToOne) {
1163                         $prev[$alias] =& $coll;
1164                     } else {
1165                         end($coll);
1166                         $prev[$alias] =& $coll[key($coll)];
1167                     }
1168                 } else {
1169                     $prev[$alias] = $coll->getLast();
1170                 }
1171             } else {
1172                 if (isset($prev[$alias])) {
1173                     unset($prev[$alias]);
1174                 }
1175             }
1176         }
1177     }
1178     /**
1179      * @return string                   returns a string representation of this object
1180      */
1181     public function __toString()
1182     {
1183         return Doctrine_Lib::formatSql($this->getQuery());
1184     }
1185 }